Next.js × DatoCMS用静态生成与 Preview Mode 构建 Headless CMS 博客的完整实践【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js本文基于 Next.js 仓库中的examples/cms-datocms示例讲解如何以 DatoCMS 作为数据源通过getStaticPaths/getStaticProps实现一个静态生成的博客站点并结合 Draft Mode预览模式在内容发布前实时预览草稿。读完后你将完整掌握从 DatoCMS 建模、环境变量配置、数据获取层设计到本地预览与部署的全流程并能读懂示例中每一处关键源码的实现意图。示例项目概述该示例展示的是 Next.js 的**静态生成Static Generation**能力文章数据来自 DatoCMS 的 GraphQL API在构建/请求时预取并生成静态页面而草稿内容则通过 Preview Mode 绕过静态产物直接实时渲染。示例采用 Pages Router 技术栈核心文件组织如下相对仓库根目录文件职责pages/index.js首页getStaticProps拉取最新文章列表pages/posts/[slug].js文章详情页getStaticPaths预生成所有文章路径pages/api/preview.js启用 Draft Mode 的 API 路由pages/api/exit-preview.js退出 Draft Mode 的 API 路由lib/api.jsDatoCMS GraphQL 数据获取层4 个查询函数lib/markdownToHtml.js将正文 Markdown 转成 HTMLlib/constants.js站点常量如 CMS 名称next.config.js配置next/image允许加载 DatoCMS 图片域名components/展示层组件文章头部、封面、正文、预览提示条等依赖方面package.json 中的关键运行时依赖为react-datocms渲染 DatoCMS 图片组件、remarkremark-htmlMarkdown 转 HTML、date-fns日期格式化、classnames样式侧使用tailwindcss、postcss、autoprefixer。快速开始用 create-next-app 引导项目执行create-next-app并指定cms-datocms示例即可引导完整项目支持 npm、Yarn 与 pnpm 三种方式npx create-next-app --example cms-datocms cms-datocms-appyarn create next-app --example cms-datocms cms-datocms-apppnpm create next-app --example cms-datocms cms-datocms-app引导完成后项目目录中会包含示例源码以及一个.env.local.example环境变量模板文件见 examples/cms-datocms/.env.local.example其中只定义了两个待填写的变量DATOCMS_API_TOKEN DATOCMS_PREVIEW_SECRETDatoCMS 端配置项目与模型设计第 1 步创建 DatoCMS 账号与项目在 DatoCMS 上注册账号后从 Dashboard 创建一个新项目New project可以选择Blank Project空白项目。第 2 步创建Author作者模型在项目的设置页新建一个Model命名为Author然后添加以下字段无需修改默认设置字段名字段类型NameText字段Single-line StringPictureMedia字段Single asset第 3 步创建Post文章模型同样新建一个Model名称为Post。重点在 Additional Settings附加设置页签中打开Enable draft/published system启用草稿/发布系统。这是后续预览模式能区分草稿与已发布内容的前提。字段清单如下除特别注明外不必修改设置字段名字段类型额外配置TitleTextSingle-line String—ContentTextMultiple-paragraph Text支持 Markdown—ExcerptTextSingle-line String—Cover ImageMediaSingle asset—DateDate and timeDate—AuthorLinksSingle link在 Validations 页签的 Accept only specified model 中选择AuthorSlugSEOSlug在 Validations 页签的 Reference field 中选择Title其中Slug字段引用Title自动生成正好对应 Next.js 端pages/posts/[slug].js的动态路由参数Author的单链接校验保证了文章只能关联作者模型与代码中author { name picture }的 GraphQL 查询结构一一对应。第 4 步录入内容在顶部Content菜单中选择Author创建一条新记录——只需要1 条即可文字可用占位数据头像可从 Unsplash 下载。选择Post创建新记录建议至少创建2 条文章Content字段支持写 Markdown封面图可从 Unsplash 下载并选择之前创建的Author。重要每条 Post 记录保存后必须点击Publish否则文章会停留在草稿draft状态不会出现在正式内容中。环境变量配置在 DatoCMS 顶部Settings菜单中点击API tokens然后复制Read-only API token只读 API token。将示例目录中的.env.local.example复制为.env.local该文件会被 Git 忽略cp .env.local.example .env.local然后在.env.local中填写两个变量变量取值说明用途DATOCMS_API_TOKEN上一步复制的只读 API token携带为Authorization: Bearer token请求 DatoCMS GraphQL APIDATOCMS_PREVIEW_SECRET任意随机字符串避免空格如MY_SECRET作为开启 Preview Mode 的身份校验密钥填写后的.env.local形如DATOCMS_API_TOKEN... DATOCMS_PREVIEW_SECRET...本地运行与预览模式第 6 步以开发模式运行 Next.jsnpm install npm run dev # 或 yarn install yarn dev博客运行在http://localhost:3000。第 7 步体验预览模式Preview Mode在 DatoCMS 中打开一篇文章修改标题例如在标题前加[Draft]点击Save但不要点击Publish此时该文章处于草稿状态。直接访问文章页不会看到新标题静态页仍是旧内容但通过Preview Mode即可看到变更。若文章没有变成草稿需回到Post模型设置的Additional Settings中确认已打开Enable draft/published system。访问以下 URL 开启预览http://localhost:3000/api/preview?secretsecretslugslugsecret填入DATOCMS_PREVIEW_SECRET对应的字符串slug文章的slug属性值可在 DatoCMS 中查看。开启后页面顶部会出现 Click here to exit preview mode 提示条点击即可退出预览对应pages/api/exit-preview.js。第 8 步部署可将项目推送到代码托管平台后导入 Vercel 进行部署。注意在 Vercel 项目设置中进入Environment Variables将DATOCMS_API_TOKEN与DATOCMS_PREVIEW_SECRET设置为与.env.local一致的值也可以使用本仓库该示例对应的 Vercel 模板一键部署部署时会要求填写上述两个环境变量。源码纵览DatoCMS 数据获取层lib/api.jslib/api.js 是示例的数据核心所有页面查询都收敛到这一层。通用请求函数与预览端点切换fetchAPI是所有 GraphQL 查询的统一入口lib/api.js#L21-L40const API_URL https://graphql.datocms.com; const API_TOKEN process.env.DATOCMS_API_TOKEN; async function fetchAPI(query, { variables, preview } {}) { const res await fetch(API_URL (preview ? /preview : ), { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${API_TOKEN}, }, body: JSON.stringify({ query, variables }), }); const json await res.json(); if (json.errors) { console.error(json.errors); throw new Error(Failed to fetch API); } return json.data; }两个值得注意的实现细节预览端点切换当preview为true时请求地址从https://graphql.datocms.com变为https://graphql.datocms.com/preview。DatoCMS 的/preview端点会返回含草稿的内容这正是预览模式能看到未发布文章的根本原因。错误处理一旦响应中包含errors立即打印并抛出Failed to fetch API让构建或 SSR 阶段尽早失败而不是渲染出残缺页面。响应式图片 Fragment示例定义了一个 GraphQL fragment用来一次性取齐 DatoCMS 响应式图片的全部信息lib/api.js#L5-L19const responsiveImageFragment fragment responsiveImageFragment on ResponsiveImage { srcSet webpSrcSet sizes src width height aspectRatio alt title bgColor base64 } ;其中webpSrcSet用于提供 WebP 变体base64用于渲染占位LQIP效果。封面图查询时通过responsiveImage(imgixParams: {fm: jpg, fit: crop, w: 2000, h: 1000})指定 imgix 图片处理参数——指定格式、裁剪方式与输出尺寸由 DatoCMS 的图片 CDN 按需产出不同分辨率的srcSet。四个查询函数函数作用关键查询点getAllPostsWithSlug构建期收集所有文章 slugallPosts { slug }供getStaticPaths生成路径列表getAllPostsForHome(preview)首页文章列表allPosts(orderBy: date_DESC, first: 20)含封面响应式图与作者信息头像裁剪为 100×100 并sat: -100去除饱和度getPostAndMorePosts(slug, preview)文章详情 推荐文章按filter: {slug: {eq: $slug}}取单篇另附morePosts: allPosts(first: 2, filter: {slug: {neq: $slug}})取两篇最新文章作延伸阅读getPreviewPostBySlug(slug)预览校验用固定preview: true只取slug用于确认该 slug 在 CMS 中真实存在源码纵览静态生成与 Draft Mode文章详情页getStaticPaths fallbackpages/posts/[slug].js 展示了完整的静态生成模式export async function getStaticProps({ params, preview false }) { const data await getPostAndMorePosts(params.slug, preview); const content await markdownToHtml(data?.post?.content || ); return { props: { preview, post: { ...data?.post, content }, morePosts: data?.morePosts ?? [], }, }; } export async function getStaticPaths() { const allPosts await getAllPostsWithSlug(); return { paths: allPosts?.map((post) /posts/${post.slug}) || [], fallback: true, }; }getStaticPaths通过getAllPostsWithSlug()在构建期枚举全部文章生成/posts/slug路径列表fallback: true表示遇到未预生成的 slug 时先渲染骨架组件中router.isFallback为真时显示 Loading…再按需生成该页的静态资源。getStaticProps的第二个参数preview由 Next.js 根据当前请求是否处于 Draft Mode 自动注入处于预览模式时它等价于true于是getPostAndMorePosts走/preview端点取草稿数据正文 Markdown 在此处经 lib/markdownToHtml.js基于 remark 体系转为 HTML 后作为 prop 传入页面。若 slug 查不到文章!post?.slug且非 fallback 状态页面渲染next/error的 404 页。首页 pages/index.js 更简洁getStaticProps({ preview false })调用getAllPostsForHome(preview)取第一条作 Hero 文章、其余进入 More Stories 列表。预览 API校验、开启与退出pages/api/preview.js 是 Draft Mode 的入口其防御逻辑值得逐段学习pages/api/preview.js#L4-L27双重校验req.query.secret必须与process.env.DATOCMS_PREVIEW_SECRET完全一致且必须携带slug任一不满足即返回401 Invalid token。slug 真实性校验调用getPreviewPostBySlug(req.query.slug)走 DatoCMS/preview端点确认该 slug 确实存在否则返回401 Invalid slug——防止任意 slug 换取预览 Cookie。开启 Draft Moderes.setDraftMode({ enable: true })通过写入 Cookie 标记后续请求处于草稿模式从而让getStaticProps的preview参数变为true。安全重定向使用307跳转到/posts/${post.slug}并且注释明确指出不直接重定向到req.query.slug以避免开放重定向open redirect漏洞。退出预览由 pages/api/exit-preview.js 完成res.setDraftMode({ enable: false })移除 Cookie 后同样以307跳回首页。页面顶部 Click here to exit preview mode 提示条即指向该路由。其他工程配置要点图片域名白名单next.config.js 通过images.remotePatterns放行https://www.datocms-assets.com/my-account/**这是next/image加载 DatoCMS 图片 CDN 资源的必要前提module.exports { images: { remotePatterns: [ { protocol: https, hostname: www.datocms-assets.com, port: , pathname: /my-account/**, }, ], }, };路径别名jsconfig.json 配置了/*指向项目根目录源码中因此可以写import { getAllPostsForHome } from /lib/api。构建脚本package.json 仅提供devnext、buildnext build、startnext start三个脚本静态生成的产物由next build在构建期产出。相关 CMS 示例Next.js 仓库为众多 headless CMS 提供了同构的博客示例数据获取层、静态生成与预览模式结构基本一致可横向对比 GraphQL/REST 接法差异相对仓库根目录的路径如下AgilityCMSBuilder.ioButterCMSContentfulCosmicDatoCMSDotCMSDrupalEnterspeedGhostGraphCMSKontent.aiMakeSwiftPayloadPlasmicPreprPrismicSanitySitecore XM CloudSitefinityStoryblokTakeShapeTinaUmbracoUmbraco heartcoreWebinyWordPressBlog Starter无 CMS 的版本小结cms-datocms示例以最小化的代码量完整演示了 headless CMS 静态生成的标准范式DatoCMS 侧通过模型与草稿/发布系统管理内容Next.js 侧用getStaticPaths/getStaticProps在构建期预生成页面用/preview端点与 Draft Mode Cookie 打通编辑中内容的实时预览并以 secret slug 双重校验、307 安全重定向保证预览入口不被滥用。这套模式可以直接迁移到 Contentful、Sanity、Prismic 等其他 CMS 示例中。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
