news 2026/9/7 3:04:00

Next.js × Cosmic 静态博客:从 getStaticPaths 到 Draft Mode 预览的完整实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Next.js × Cosmic 静态博客:从 getStaticPaths 到 Draft Mode 预览的完整实现解析

Next.js × Cosmic 静态博客:从 getStaticPaths 到 Draft Mode 预览的完整实现解析

【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js

本篇基于 Next.js 官方示例仓库中的 cms-cosmic 示例 展开,讲解如何以 Cosmic 作为 Headless CMS 数据源,利用 Next.js 的静态生成(Static Generation)构建一个博客站点。读完本文,你将完整掌握该示例的搭建步骤、三个环境变量与 Cosmic API 的对应关系、getStaticPaths的 fallback 机制、以及从/api/previewsetDraftMode的草稿预览(Preview/Draft Mode)全链路实现原理。

一、示例定位:静态生成博客 + Headless CMS

cms-cosmic 是 Next.js 仓库examples/目录下的一组 CMS 集成示例之一。它的核心定位是:展示如何用 Next.js 的静态生成功能,从 Cosmic(一种 Hosted Headless CMS)拉取文章数据,在构建期/请求期渲染为静态页面,并支持 CMS 后台的草稿预览

与示例配套的源码结构如下:

  • pages/index.tsx:首页,通过getStaticProps拉取全部文章;
  • pages/posts/[slug].tsx:文章详情页,通过getStaticPaths+getStaticProps实现动态路径的静态生成;
  • pages/api/preview.ts、pages/api/exit-preview.ts:预览模式开启/关闭的 API 路由;
  • lib/api.tsx:封装 Cosmic SDK 的数据获取函数,是所有数据请求的唯一出口;
  • lib/markdownToHtml.ts:Markdown 到 HTML 的转换;
  • interfaces/index.ts:PostType等 TypeScript 类型定义;
  • components/:布局与文章展示组件(预览提示条、文章头、正文等);
  • next.config.js:Next.js 配置,重点是图片域名白名单。

整个示例的依赖非常精简,从 package.json 可以看到:数据层依赖cosmicjs(Cosmic 官方 SDK),Markdown 渲染依赖remark+remark-html,日期处理用date-fns,样式用 Tailwind CSS。

二、快速启动示例

仓库 README 给出三种包管理器方式,使用create-next-app引导式创建示例项目:

# npm npx create-next-app --example cms-cosmic cms-cosmic-app
# Yarn yarn create next-app --example cms-cosmic cms-cosmic-app
# pnpm pnpm create next-app --example cms-cosmic cms-cosmic-app

执行后会得到一个名为cms-cosmic-app的独立项目,其中已包含示例目录下的全部源码与.env.local.example模板文件。

三、配置 Cosmic 账户与三个环境变量

Step 1:创建账户并安装 App

先在 Cosmic 官网注册账户,然后从 Cosmic App Marketplace 安装Next.js Static Blog应用。这个步骤会引导你在 Cosmic 后台创建好 Bucket、posts 数据模型(title、slug、excerpt、cover_image、author、content 等字段),是后续所有 API 调用能命中数据的前提。

Step 2:准备.env.local

进入 Cosmic 后台的Settings > Basic Settings,然后复制示例目录中的环境变量模板:

cp .env.local.example .env.local

模板文件.env.local.example内容只有三行:

COSMIC_BUCKET_SLUG= COSMIC_READ_KEY= COSMIC_PREVIEW_SECRET=

.env.local中填入实际值:

  • COSMIC_BUCKET_SLUG:Cosmic 后台API Access区域的Bucket slug
  • COSMIC_READ_KEYAPI Access区域的Read Key(只读密钥,足够本示例的读场景使用);
  • COSMIC_PREVIEW_SECRET:任意随机字符串(避免空格),用于预览模式的身份校验。

这三个变量与源码的对应关系可以从 lib/api.tsx 得到印证:

const BUCKET_SLUG = process.env.COSMIC_BUCKET_SLUG; const READ_KEY = process.env.COSMIC_READ_KEY; const bucket = Cosmic().bucket({ slug: BUCKET_SLUG, read_key: READ_KEY, });

COSMIC_PREVIEW_SECRET只在 pages/api/preview.ts 中参与校验(见下文预览模式一节)。

Step 3:启动开发模式

npm install npm run dev # 或 yarn install yarn dev

启动后博客运行在http://localhost:3000

四、静态生成链路:getStaticPaths 与 getStaticProps

首页:全量文章拉取

pages/index.tsx 的getStaticProps调用 lib/api.tsx 中的getAllPostsForHome

export const getAllPostsForHome = async (preview: boolean): Promise<PostType[]> => { const params = { query: { type: "posts", }, props: "title,slug,metadata,created_at", sort: "-created_at", ...(preview && { status: "any" }), }; const data = await bucket.getObjects(params); return data.objects; };

注意几个 Cosmic API 的用法细节:

  • query: { type: "posts" }限定只查 posts 类型对象;
  • props字段白名单让响应只携带渲染首页所需的字段,减少传输量;
  • sort: "-created_at"按创建时间倒序,首页据此把第一篇作为 Hero 大图文章、其余进入"更多文章"列表(见 index.tsx 中allPosts[0]allPosts.slice(1)的切分);
  • ...(preview && { status: "any" })是预览模式的关键开关:只有处于预览态时,才向 Cosmic 追加status: "any",从而把草稿(draft)状态的文章也一并返回;正常构建时只会拿到已发布(published)内容。

文章页:动态路径 + fallback

pages/posts/[slug].tsx 中的getStaticPaths在构建期向 Cosmic 查询全部文章 slug:

export async function getStaticPaths() { const allPosts = (await getAllPostsWithSlug()) || []; return { paths: allPosts.map((post) => `/posts/${post.slug}`), fallback: true, }; }
  • getAllPostsWithSlug只请求props: "slug"(见 lib/api.tsx),是最轻量的查询;
  • fallback: true意味着构建期未包含的 slug 不会直接 404,而是在首次访问时触发getStaticProps按需生成并缓存——这对"CMS 里随时可能新增文章"的场景非常关键。

页面组件里对应的兜底逻辑是:

const router = useRouter(); if (!router.isFallback && !post?.slug) { return <ErrorPage statusCode={404} />; }

即 fallback 期间显示 "Loading…" 占位标题,若最终拿不到文章则渲染 404(见 [slug].tsx)。

getStaticProps侧调用getPostAndMorePosts(slug, preview)一次拿到当前文章 + 最多 2 篇"相关文章"(Cosmic 查询limit: 3,再过滤掉当前 slug 后slice(0, 2),见 lib/api.tsx),并把metadata.content中的 Markdown 在服务端转成 HTML:

const content = await markdownToHtml(data["post"]?.metadata?.content || "");

Markdown 渲染

lib/markdownToHtml.ts 的完整实现只有 7 行:

import { remark } from "remark"; import html from "remark-html"; const markdownToHtml = async (markdown: string) => { const result = await remark().use(html).process(markdown); return result.toString(); }; export default markdownToHtml;

由于转换发生在getStaticProps中,客户端收到的已经是最终 HTML,浏览器端无需任何 Markdown 解析开销。文章数据结构由 interfaces/index.ts 定义:PostType包含titleslugcontentcreated_at以及metadata(封面图cover_image、作者author、摘要excerpt),其中图片类型为ImgixType(同时保存urlimgix_url),为下一节的图片处理做准备。

五、Preview Mode(Draft Mode):从 CMS 后台一键预览草稿

这是本示例最有实战价值、也最能体现 Next.js 静态生成与 CMS 配合技巧的部分。原文档 Step 5 的操作流程与源码实现一一对应:

1. 在 Cosmic 后台配置 Preview Link

进入Posts > Edit Settings,在 "Preview Link" 区域填入(README 原图展示了 Cosmic 后台该区域的截图位置):

http://localhost:3000/api/preview?secret=<secret>&slug=[object_slug]
  • <secret>即你在.env.local中设置的COSMIC_PREVIEW_SECRET
  • [object_slug]是 Cosmic 的 shortcode,点击时会被自动替换为该文章的slug字段值。

2./api/preview的安全校验与开启

pages/api/preview.ts 的完整逻辑值得逐段对照阅读:

export default async function preview(req, res) { // 校验 secret 与 slug 参数;secret 只应被本路由和 CMS 知道 if ( req.query.secret !== process.env.COSMIC_PREVIEW_SECRET || !req.query.slug ) { return res.status(401).json({ message: "Invalid token" }); } // 回查 CMS,确认该 slug 真实存在 const post = await getPreviewPostBySlug(req.query.slug); if (!post) { return res.status(401).json({ message: "Invalid slug" }); } // 通过设置 Cookie 开启 Draft Mode res.setDraftMode({ enable: true }); // 重定向到从 CMS 取回的 slug 对应路径 // 注意:不使用 req.query.slug 重定向,避免开放重定向(open redirect)漏洞 res.writeHead(307, { Location: `/posts/${post.slug}` }); res.end(); }

三个要点:

  1. 双重校验:先比对COSMIC_PREVIEW_SECRET,再调用getPreviewPostBySlug回查 Cosmic。该函数(lib/api.tsx)携带status: "any"查询,即草稿也能查到——这正是"COSMIC 里有草稿但页面没发布"时预览依然生效的原因;
  2. res.setDraftMode({ enable: true }):示例采用的是 Next.js 新版 Draft Mode API(通过设置专用 Cookie 开启),而非旧版setPreviewData。开启后,该浏览器的请求命中getStaticProps时会自动带上preview: true参数——这正是前面getAllPostsForHome(preview)getPostAndMorePosts(slug, preview)签名中preview参数的来源,它触发status: "any",让草稿数据流入静态渲染;
  3. 防开放重定向:代码注释明确说明,重定向目标不是用户传入的req.query.slug,而是从 CMS 回查结果里取回的post.slug,避免构造恶意slug把用户重定向到外部站点。

3. 草稿态的完整体验

按 README 描述的实验流程:把某篇文章标题改成带[Draft]前缀,只点Save Draft不点 Publish。此时:

  • 直接访问该文章页 → 看到旧标题(静态页面仍是已发布版本);
  • 点击 Cosmic 后台的Preview Link按钮 → 走/api/preview开启 Draft Mode 后 307 重定向到/posts/[slug]→ 看到带[Draft]的新标题。

4. 退出预览

页面顶部的提示条由 components/alert.tsx 渲染:处于预览态时显示 "This page is a preview. Click here to exit preview mode.",链接指向/api/exit-preview。该路由(pages/api/exit-preview.ts)实现极简:

export default function exit(_, res) { // 通过移除 Cookie 退出 Draft Mode res.setDraftMode({ enable: false }); res.writeHead(307, { Location: "/" }); res.end(); }

六、图片配置:Cosmic 的 Imgix 域名白名单

Cosmic 的图片托管基于 Imgix,文章的imgix_url指向imgix.cosmicjs.com。为了让<Image>组件合法加载这些远程图,next.config.js 配置了remotePatterns

module.exports = { images: { remotePatterns: [ { protocol: "https", hostname: "imgix.cosmicjs.com", port: "", pathname: "/my-account/**", }, ], }, };

从源码结构看,pathname前缀/my-account/**是 Cosmic Bucket 图片 URL 的公共前缀(my-account为占位命名,实际 URL 以各自 Bucket 为准)。配置时若域名不匹配,next/image会在开发期报 "doesn't match any configured remotepatterns" 错误(对应仓库错误文档 next-image-unconfigured-localpatterns.mdx)。

七、部署

本地开发验证通过后,按 README 的 Step 6,可将项目推送到 Git 仓库再导入 Vercel 部署。部署时的关键动作是在 Vercel 的Environment Variables中配置与.env.local相同的三个变量(COSMIC_BUCKET_SLUGCOSMIC_READ_KEYCOSMIC_PREVIEW_SECRET),否则构建期拉取数据会因缺少凭证而失败。README 同时提供了基于官方模板的一键 Deploy 入口,其中预设了同样这三个环境变量的名称。

八、小结与延伸阅读

cms-cosmic 示例的价值在于用最小的代码量串起了 SSG 博客的三个工程要点:构建期数据预取(getStaticPaths+fallback: true按字段白名单 +status: "any"精细控制 CMS 查询、以及secret 校验 +setDraftMode+ 安全重定向组成的草稿预览闭环。这套模式同样适用于接入其他 Headless CMS。

仓库中还有大量结构相近的 CMS 集成示例可作对照参考,例如:Contentful、Sanity、Prismic、WordPress、Payload、Tina,以及不带 CMS 的基础博客模板 Blog Starter。

【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 3:03:10

加扰与解扰:从伪随机序列到时钟恢复的工程实战解析

简介&#xff1a;面向数字通信与FPGA开发者的VHDL加扰与解扰工程包&#xff0c;完整演示了从算法建模到硬件验证的流程。加扰用于将连续1/0序列随机化&#xff0c;降低信道中的自相关干扰&#xff1b;解扰则在接收端恢复原始数据&#xff0c;是数字电视、LTE/5G及卫星通信的常见…

作者头像 李华
网站建设 2026/9/7 3:03:07

C盘清理实战:从空间分析到自动化脚本,Windows系统优化完整指南

C盘红色条又快撑满的时候&#xff0c;很多人的第一反应是下载一个“C盘清理神器”。这类工具在搜索结果里非常多&#xff0c;标题也基本都会带上“系统优化、一键清理、完全免费”这些词。说实话&#xff0c;Windows环境下确实需要定期做C盘维护&#xff0c;但真正该做的第一件…

作者头像 李华
网站建设 2026/9/7 3:02:55

i.MX6ULL平台Linux驱动:Platform机制与设备树匹配全解析

1. 先聊聊为什么Linux驱动必须搞懂Platform机制做了几个月的裸机驱动&#xff0c;或者刚写完几个字符设备驱动的新手&#xff0c;大概率会遇到一个困惑&#xff1a;我在x86的虚拟机上写的hello驱动&#xff0c;怎么换到i.MX6ULL这种ARM板卡上就跑不通&#xff1f;原因当然不只是…

作者头像 李华
网站建设 2026/9/7 3:01:46

2026 研发团队协作优化方案:AI 自动生成交接文档与 PR 提交说明

开发者的核心价值本应聚焦业务逻辑设计与核心功能研发&#xff0c;但现实中&#xff0c;超过六成的工作时间被源码梳理、架构拆解、文档编写、缺陷排查等重复性事务挤占。借助面向本地项目的 AI 智能助手承接基础事务&#xff0c;可将新项目摸底周期从数天压缩至数分钟&#xf…

作者头像 李华
网站建设 2026/9/7 3:00:09

LangChain多智能体实战:用Streamlit快速搭建婚礼策划师

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华