Next.js × Sitecore XM Cloud:基于 JSS 渲染 SDK 搭建 Headless CMS 站点的完整实践
本文基于 Next.js 官方仓库中的 cms-sitecore-xmcloud 示例 展开,讲解如何使用 Sitecore JavaScript Rendering SDK(JSS)将 Next.js 应用接入 Sitecore XM Cloud 站点,实现 headless SXA(Sitecore Experience Accelerator)组件渲染。读完后,你将掌握该示例所需的全部环境变量配置、create-next-app 脚手架与 npm 脚本用法、构建期 bootstrap 代码生成流程、Next.js 与 Sitecore 之间的请求重写策略,以及 SSG + ISR 的页面渲染模型,并理解 Sitecore 侧配置补丁如何与前端应用协同工作。
示例定位:JSS SDK + headless SXA
该示例是一个通过 Sitecore JSS for Next.js SDK 连接 Sitecore XM Cloud 站点的完整 Next.js 项目,内置了一组示例组件与 headless SXA 的配置。其核心特点(由 package.json 与源码可确认):
- 依赖
@sitecore-jss/sitecore-jss-nextjs(约 21.1.6 版本)与@sitecore-jss/sitecore-jss-cli,并基于next^13.1.6、react^18.2.0 构建; package.json中的config字段声明了 JSS 应用身份:appName为xmcloud-nextjs-starter、根占位符rootPlaceholders为jss-main、配置补丁部署路径sitecoreConfigPath为/App_Config/Include/zzz、GraphQL Edge 端点路径graphQLEndpointPath为/sitecore/api/graph/edge,并且templates同时包含nextjs与nextjs-sxa,即同时启用 JSS 基础模板与 headless SXA 模板;- 样式层引入了
bootstrap5、font-awesome与完整的 SASS 变体体系(见 src/assets),用于承载 headless SXA 组件的预设样式变体(如promo、navigation、rich-text等目录)。
关于如何从零创建并部署 XM Cloud 项目的完整流程,官方建议参考 Vercel 的 Sitecore XM Cloud 集成指南与 Sitecore 官方文档(示例 README 中的外部链接),本文则聚焦仓库内可直接验证的实现细节。
环境变量:连接 XM Cloud 的钥匙
示例 README 列出了部署时必须提供的 5 个环境变量,完整的 .env.example 还补充了若干开发期变量。逐一说明:
| 变量 | 作用 | 来源 |
|---|---|---|
JSS_APP_NAME |
在 XM Cloud 中配置的 JSS 应用名称 | README 要求必填 |
GRAPH_QL_ENDPOINT |
GraphQL Edge 端点,Sitecore Experience Edge 必需 | README 要求必填 |
SITECORE_API_KEY |
Sitecore API Key,构建应用所必需 | README 要求必填 |
SITECORE_API_HOST |
Sitecore API 主机名 | README 要求必填 |
FETCH_WITH |
获取 Sitecore API 数据的方式,取值为 GraphQL 或 REST |
README 要求必填 |
PUBLIC_URL |
用于绝对 URL 的公共地址(编辑器内运行 Next.js 应用时需要)。必须与 Sitecore 配置中的 serverSideRenderingEngineApplicationUrl 一致,随公共端点变化而更新 |
.env.example |
JSS_EDITING_SECRET |
保护 Sitecore 编辑器端点(默认 /api/editing/render)的密钥。该客户端值必须与服务端值(Sitecore 配置补丁中的 JavaScriptServices.ViewEngine.Http.JssEditingSecret)一致,建议至少 16 位字母数字 |
.env.example |
DEFAULT_LANGUAGE |
应用的默认语言 | .env.example |
DISABLE_SSG_FETCH |
设置为 true 时跳过 getStaticPaths 的预渲染抓取,启用完整的 ISR(增量静态再生成)流程 |
.env.example |
DEBUG |
Sitecore JSS 的 npm 包使用 debug 模块输出日志,如 DEBUG=sitecore-jss:* 查看全部日志,DEBUG=sitecore-jss:layout 只看 Layout Service 日志 |
.env.example |
两点值得注意(均可在源码中验证):
SITECORE_API_KEY与SITECORE_API_HOST之所以被标注为“构建时必需”,是因为它们通常保存在scjssconfig.json(分别为sitecore.apiKey与sitecore.layoutServiceHost),而该文件在本地从未执行过jss setup、或处于忽略源码管理的更高环境时可能不存在,此时需要通过环境变量在构建期提供;.env.example明确提示 Next.js 原生支持.env.local(且已加入 git 忽略),本地开发时应使用它存放敏感值。
脚手架与 npm 脚本
README 给出了通过 create-next-app 引导示例的三种方式:
npx create-next-app --example cms-sitecore-xmcloud cms-sitecore-xmcloud-app
yarn create next-app --example cms-sitecore-xmcloud cms-sitecore-xmcloud-app
pnpm create next-app --example cms-sitecore-xmcloud cms-sitecore-xmcloud-app
脚手架完成后,日常开发由 package.json 中的 scripts 驱动,核心命令如下:
| 命令 | 作用 |
|---|---|
npm run dev |
等价于 cross-env NODE_OPTIONS='--inspect' next dev,带 Node 调试端口启动开发服务器 |
npm run start |
等价于 next start,启动生产服务器 |
npm run jss |
调用 Sitecore JSS CLI(jss),用于 setup、deploy config 等操作 |
npm run bootstrap |
以 ts-node 运行 scripts/bootstrap.ts,生成构建所需的临时 JS 文件(见下节) |
npm run build |
npm-run-all --serial bootstrap next:build,即先执行 bootstrap 再生成 Next.js 构建产物 |
npm run graphql:update |
运行 scripts/fetch-graphql-introspection-data.ts,拉取 GraphQL 内省数据以更新类型定义 |
npm run scaffold |
运行 scripts/scaffold-component.ts 生成新组件骨架 |
npm run start:connected |
串行执行 bootstrap 后,并行启动 next dev 与 generate-component-factory.ts --watch,即“连接模式”:组件工厂随组件源码变化实时重新生成 |
npm run start:production |
串行执行 bootstrap、next build、next start |
其中 start:connected 是该示例开发体验的关键:连接 Sitecore 本地站点时,sitecore-jss-cli 会持续监听组件目录并将组件名到 React 组件的映射写入工厂文件,保证 Editor 中新增/重命名组件后无需重启即可被前端识别。
构建期 bootstrap:插件、配置与组件工厂的代码生成
build 命令之所以先跑 bootstrap,是因为 Next.js 需要导入一批在构建前动态生成的模块。从 scripts/bootstrap.ts 可以看到它依次执行三个阶段:
- 插件生成(generate-plugins.ts):按约定扫描各
src/lib/*/plugins目录,自动生成src/temp下的插件清单模块。示例中定义了 7 组插件映射,覆盖了 config、sitemap-fetcher、middleware、page-props-factory、next-config、extract-path、site-resolver 七个工厂。命名约定是:文件名去掉扩展名后,按连字符大驼峰化并追加Plugin后缀(如preview-mode.ts映射为previewModePlugin)。这套机制意味着后续扩展页面 props 的处理逻辑时,只需在对应plugins目录下新增文件即可自动注册; - 配置生成(generate-config.ts):将运行时配置写入
src/temp/config.js。值得注意的是其写入策略——每个配置项都生成为config.xxx = process.env.XXX || '<默认值>'的形式,即环境变量优先、默认值兜底,这也是为什么SITECORE_API_KEY等值既能写进scjssconfig.json也能通过环境变量注入; - 组件工厂生成(generate-component-factory.ts):生成
temp/componentFactory,提供componentFactory(生产渲染)与editingComponentFactory(编辑器渲染)两套组件名到组件的映射。
Next.js 与 Sitecore 的集成:重写、国际化与资源前缀
next.config.js 是前后端集成的枢纽,值得逐项拆解:
const jssConfig = require("./src/temp/config");
const { getPublicUrl } = require("@sitecore-jss/sitecore-jss-nextjs");
const plugins = require("./src/temp/next-config-plugins") || {};
const publicUrl = getPublicUrl();
const nextConfig = {
assetPrefix: publicUrl,
distDir: process.env.NEXTJS_DIST_DIR || ".next",
env: {
PUBLIC_URL: publicUrl,
},
i18n: {
locales: ["en"],
defaultLocale: jssConfig.defaultLanguage,
},
reactStrictMode: true,
async rewrites() {
// 连接模式下将 Sitecore 路径代理到 Sitecore
return [
{ source: "/sitecore/api/:path*", destination: `${jssConfig.sitecoreApiHost}/sitecore/api/:path*` },
{ source: "/-/:path*", destination: `${jssConfig.sitecoreApiHost}/-/:path*` },
{ source: "/layouts/system/:path*", destination: `${jssConfig.sitecoreApiHost}/layouts/system/:path*` },
{ source: "/healthz", destination: "/api/healthz" },
{ source: "/sitecore/service/:path*", destination: `${jssConfig.sitecoreApiHost}/sitecore/service/:path*` },
];
},
};
module.exports = () => {
return Object.values(plugins).reduce((acc, plugin) => plugin(acc), nextConfig);
};
几个设计要点:
- 重写规则是“连接模式”(connected mode)的核心:
/sitecore/api/*代理 Layout Service 等 API,/-/*代理媒体文件,/layouts/system/*用于访客识别(visitor identification),/sitecore/service/*代理 Sitecore 服务页面。结合 Sitecore 配置中IncludeServerUrlInMediaUrls=false的设置,媒体请求全部经由 Next.js 转发,避免把 Sitecore 服务器直接暴露给公网; i18n路由:locales应覆盖(或为子集于)Sitecore 中配置的语言,访问不带语言前缀的路径时回落到defaultLanguage;- 插件化的配置导出:
module.exports是一个函数,会把nextConfig依次交给src/temp/next-config-plugins中的插件变换。查看 src/lib/next-config/plugins 目录可知内置了graphql.js、robots.js、sass.js、sitemap.js四个插件,分别负责注入 GraphQL 端点处理、robots.txt 与 sitemap 生成、SASS 编译等构建行为; NEXTJS_DIST_DIR允许在容器化并发运行时指定不同的产物目录。
渲染模型:Catch-all 路由 + SSG + ISR
示例采用 Pages Router 的单一 catch-all 页面承载整个站点:src/pages/[[...path]].tsx 负责所有路径的渲染。其数据获取逻辑体现了 SSG 与 ISR 的组合使用:
export const getStaticPaths: GetStaticPaths = async (context) => {
let paths: StaticPath[] = [];
let fallback: boolean | "blocking" = "blocking";
if (process.env.NODE_ENV !== "development" && !process.env.DISABLE_SSG_FETCH) {
try {
// 生产/构建模式下,从 Sitecore 抓取站点地图以预渲染全部页面
paths = await sitemapFetcher.fetch(context);
} catch (error) {
console.log("Error occurred while fetching static paths");
}
fallback = process.env.EXPORT_MODE ? false : fallback;
}
return { paths, fallback };
};
export const getStaticProps: GetStaticProps = async (context) => {
const props = await sitecorePagePropsFactory.create(context);
return {
props,
// 最多每 5 秒重新生成一次
revalidate: 5,
notFound: props.notFound,
};
};
从源码结构看,这个策略是双模式的:
- 构建/生产模式下(非 development 且未设置
DISABLE_SSG_FETCH),sitemapFetcher(其插件位于 src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts)会向 Sitecore 请求站点地图,把所有页面路径作为getStaticPaths的返回值预先静态渲染; - 开发模式或显式设置
DISABLE_SSG_FETCH=true时,返回空paths+fallback: "blocking",所有页面按需渲染,配合revalidate: 5形成完整的 ISR 流程——请求到来时若页面过期则阻塞并重新生成。
页面组件本身还处理了两种编辑态:
pageEditing为 true 时切换到editingComponentFactory;renderingType === RenderingType.Component时渲染EditingComponentPlaceholder,即组件级渲染——Sitecore 编辑器修改单个组件时无需刷新整页;- 由于 Sitecore 编辑器不支持 Fast Refresh,页面挂载后通过
handleEditorFastRefresh()在快速刷新完成后刷新编辑器 chrome。
页面 props 由 sitecorePagePropsFactory 生成,其插件目录 src/lib/page-props-factory/plugins 包含 component-props、normal-mode、preview-mode、site 四个插件,分别负责组件 props 解析、普通模式取数、预览模式取数与站点信息注入——预览模式与正常模式走不同取数路径,正是编辑器实时预览与生产渲染分离的实现基础。
数据获取:GraphQL 或 REST
.env.example 中 FETCH_WITH 默认取值为 GraphQL。该示例的取数实现是 src/lib/data-fetcher.ts:
import { AxiosDataFetcher, AxiosResponse } from "@sitecore-jss/sitecore-jss-nextjs";
export function dataFetcher<ResponseType>(
url: string,
data?: unknown,
): Promise<AxiosResponse<ResponseType>> {
return new AxiosDataFetcher().fetch<ResponseType>(url, data);
}
它基于 SDK 提供的 AxiosDataFetcher,源码注释明确说明这是一个可替换的实现——只要符合 HttpDataFetcher<T> 的约束,即可换成任意支持 SSR 的 HTTP/fetch 库。Layout 与 Dictionary 数据则分别由 layout-service-factory.ts 与 dictionary-service-factory.ts 根据 FETCH_WITH 选择 GraphQL 或 REST 通道。多语言字典通过 src/pages/_app.tsx 中的 next-localization(Rossetta)注入 I18nProvider,源码注释也提醒:Next.js 只提供 i18n 路由而不提供翻译能力,单语言应用可移除相关引用。
此外,src/pages/api 下还实现了四个 API 路由:editing/render.ts(编辑器渲染端点,受 JSS_EDITING_SECRET 保护)、healthz.ts(健康检查,供 rewrites 中的 /healthz 转发命中)、robots.ts 与 sitemap.ts(由 next-config 插件接管生成)。
Sitecore 侧配置补丁与前端的双向约定
前端的种种行为在 Sitecore 侧有对应约定,见配置补丁 sitecore/config/xmcloud-nextjs-starter.config。该文件由 jss deploy config 部署(目标路径取自 package.json 的 sitecoreConfigPath,即 /App_Config/Include/zzz),关键内容包括:
- 站点注册(
<sites>节点):注册名为xmcloud-nextjs-starter的站点,hostName为xmcloud-nextjs-starter.dev.local,rootPath为/sitecore/content/xmcloud-nextjs-starter,startItem为/home。注释特别强调:JSS 站点默认以 live 模式运行(database="master"),便于开发但禁用了 workflow 与发布,上线前必须改为web; - JSS 应用注册(
<javaScriptServices><apps>节点):graphQLEndpoint="/sitecore/api/graph/edge"启用 Integrated GraphQL,不用可移除;layoutServiceConfiguration使用 GraphQL Edge schema 时应为default;serverSideRenderingEngineEndpointUrl="http://localhost:3000/api/editing/render"指向前端的编辑器端点,serverSideRenderingEngineApplicationUrl="http://localhost:3000"必须与环境变量PUBLIC_URL一致——这就是 .env.example 中反复强调“随公共端点变化而更新”的原因。
- 媒体缩放白名单(
<allowedMediaParams>):JSS 的服务端媒体缩放(<Image/>帮助组件的imageParams/srcSetprops)要求参数集合白名单化以防 DoS 攻击。示例中提供了styleguide-image-sample(mw=100,mh=50)与next-image-default(对应 Next.js 图片组件的默认宽度集合:16、32、48、64、96、128、256、384、640、750、828、1080、1200、1920、2048、3840)两组,未在白名单中的参数组合将返回未缩放的原始图片; - 媒体 URL 解析(
<layoutService>节点):对default(GraphQL Edge 请求)与jss(Layout Service REST 请求)两套配置均设置IncludeServerUrlInMediaUrls=false,让媒体请求不带 Sitecore 服务器 URL,从而全部走 Next.js 的rewrites转发,避免直接暴露 Sitecore 服务器; - 可选设置:
Analytics.ForwardedRequestHttpHeader(让分析模块读取X-Forwarded-For以记录真实客户端 IP)、开发期机器人检测开关、Experience Edge 的 item/field 级语言回退开关。
内置的 headless SXA 组件
src/components 目录随示例提供了一组 headless SXA 渲染组件,可直接作为组件开发模板参考:
- 基础布局:
Container、PageContent、Title、Promo、Navigation、RichText、LinkList、Image; - SXA 分割器:
RowSplitter、ColumnSplitter(配合 ContentBlock 处理 SXA 布局块); - 动态占位符:
PartialDesignDynamicPlaceholder(承载 SXA partial design 的动态内容区域); - 前端即服务(FEaaS)包装:
FEaaSWrapper(Sitecore FEaaS 场景下的容器组件)。
这些组件对应 templates 中的 nextjs-sxa 模板产物,SXA 组件的预设样式变体则集中在 src/assets/sass/variants(link-list、navigation、page-content、promo、rich-text、title 六个变体目录)。新组件可通过 npm run scaffold 脚手架生成,并以 npm run start:connected 在连接模式下热更新组件工厂。
小结与相关示例
回顾该示例的完整工作链路:bootstrap 在构建前生成插件清单、运行时配置(环境变量优先)与组件工厂 → next.config.js 完成重写代理、i18n 与插件化配置 → catch-all 页面通过 sitemapFetcher 预取路径实现 SSG,或以 fallback: "blocking" + revalidate: 5 走 ISR → page-props-factory 区分正常/预览模式取数 → 编辑器端点 /api/editing/render 受 JSS_EDITING_SECRET 保护并支持组件级局部刷新。整条链路的前后端契约(API 主机、应用名、编辑端点、媒体白名单)由环境变量与 Sitecore 配置补丁共同维护,两侧取值必须严格一致。
Next.js 仓库的 examples 目录下还有大量其他 CMS 集成示例可作对照参考:AgilityCMS、Builder.io、ButterCMS、Contentful、Cosmic、DatoCMS、DotCMS、Drupal、Enterspeed、Ghost、GraphCMS、Kontent.ai、MakeSwift、Payload、Plasmic、Prepr、Prismic、Sanity、Sitefinity、Storyblok、TakeShape、Tina、Umbraco、Umbraco Heartcore、Webiny、WordPress 以及 Blog Starter。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00