如何在 Next.js 中使用草稿模式预览内容

页面文档数据获取文档中,我们讨论了如何使用 getStaticPropsgetStaticPaths 在构建时预渲染页面(静态生成)。

当页面从无头 CMS 获取数据时,静态生成非常有用。但如果您正在无头 CMS 上撰写草稿并希望立即在页面上查看,这种方式就不理想了。此时您会希望 Next.js 在请求时而非构建时渲染这些页面,并获取草稿内容而非已发布内容。您需要 Next.js 仅在此特定情况下绕过静态生成。

Next.js 的草稿模式 (Draft Mode) 功能正是为此而生。以下是使用指南。

第一步:创建并访问 API 路由

如果您不熟悉 Next.js API 路由,请先查阅 API 路由文档

首先创建 API 路由,可任意命名(例如 pages/api/draft.ts)。

在此 API 路由中,需在响应对象上调用 setDraftMode

export default function handler(req, res) {
  // ...
  res.setDraftMode({ enable: true })
  // ...
}

这将设置一个Cookie来启用草稿模式。后续携带此 Cookie 的请求将触发草稿模式,从而改变静态生成页面的行为(下文详述)。

您可以通过手动创建如下 API 路由并从浏览器访问来测试:

pages/api/draft.ts
// 用于从浏览器手动测试的简单示例
export default function handler(req, res) {
  res.setDraftMode({ enable: true })
  res.end('草稿模式已启用')
}

如果在浏览器开发者工具中访问 /api/draft,会注意到响应头中有 Set-Cookie 字段,其值为名为 __prerender_bypass 的 Cookie。

从无头 CMS 安全访问

实际应用中,您需要从无头 CMS安全地调用此 API 路由。具体步骤因 CMS 而异,但以下是通用方案:

此方案假设您的无头 CMS 支持设置自定义草稿 URL。若不支持,您仍可用此方法保护草稿 URL,但需手动构建和访问。

首先,使用任意令牌生成器创建密钥字符串。该密钥仅由 Next.js 应用和无头 CMS 知晓,可防止无 CMS 访问权限者获取草稿 URL。

其次,若 CMS 支持自定义草稿 URL,请将以下内容设为草稿 URL(假设草稿 API 路由位于 pages/api/draft.ts):

终端
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site> 替换为部署域名
  • <token> 替换为生成的密钥
  • <path> 替换为目标页面路径(如欲查看 /posts/foo,则使用 &slug=/posts/foo

某些 CMS 允许在草稿 URL 中包含变量,从而动态设置 <path>,例如:&slug=/posts/{entry.fields.slug}

最后,在草稿 API 路由中:

  • 检查密钥是否匹配及 slug 参数是否存在(否则请求应失败)
  • 调用 res.setDraftMode
  • 重定向浏览器到 slug 指定路径(下例使用 307 重定向
export default async (req, res) => {
  // 验证密钥和必要参数
  // 此密钥应仅限此API路由和CMS知晓
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: '无效令牌' })
  }

  // 向无头CMS查询该slug是否存在
  // getPostBySlug需实现针对无头CMS的获取逻辑
  const post = await getPostBySlug(req.query.slug)

  // 若slug不存在则阻止启用草稿模式
  if (!post) {
    return res.status(401).json({ message: '无效slug' })
  }

  // 通过设置Cookie启用草稿模式
  res.setDraftMode({ enable: true })

  // 重定向到获取的文章路径
  // 不直接重定向到req.query.slug以防开放重定向漏洞
  res.redirect(post.slug)
}

成功后,浏览器将携带草稿模式 Cookie 重定向到目标路径。

第二步:更新 getStaticProps

接下来需更新 getStaticProps 以支持草稿模式。

若请求的页面具有 getStaticProps 且已设置 Cookie(通过 res.setDraftMode),则 getStaticProps 将在请求时被调用(而非构建时)。

此外,其调用时将接收 context 对象,其中 context.draftModetrue

export async function getStaticProps(context) {
  if (context.draftMode) {
    // 动态数据
  }
}

由于我们在草稿 API 路由中使用了 res.setDraftMode,故 context.draftMode 将为 true

若同时使用 getStaticPaths,则 context.params 也可用。

获取草稿数据

可根据 context.draftMode 修改 getStaticProps 以获取不同数据。

例如,无头 CMS 可能有专用的草稿文章 API 端点。此时可如下修改端点 URL:

export async function getStaticProps(context) {
  const url = context.draftMode
    ? 'https://draft.example.com'
    : 'https://production.example.com'
  const res = await fetch(url)
  // ...
}

完成!现在从无头 CMS 或手动访问草稿 API 路由(携带 secretslug)即可查看草稿内容。当您更新草稿但未发布时,也能立即查看。

将此 URL 设为无头 CMS 的草稿 URL 或手动访问:

终端
https://<your-site>/api/draft?secret=<token>&slug=<path>

更多细节

默认情况下,草稿模式会话在浏览器关闭时结束。

要手动清除 Cookie,可创建调用 setDraftMode({ enable: false }) 的 API 路由:

pages/api/disable-draft.ts
export default function handler(req, res) {
  res.setDraftMode({ enable: false })
}

然后向 /api/disable-draft 发送请求。若使用 next/link 调用此路由,必须传递 prefetch={false} 以防预取时意外删除 Cookie。

getServerSideProps 协作

草稿模式可与 getServerSideProps 协同工作,在上下文对象中以 draftMode 键存在。

须知:使用草稿模式时不应设置 Cache-Control 标头,因其无法被绕过。建议改用 ISR

与 API 路由协作

API 路由可通过请求对象的 draftMode 访问该状态。例如:

export default function myApiRoute(req, res) {
  if (req.draftMode) {
    // 获取草稿数据
  }
}

每次 next build 生成唯一值

每次运行 next build 都会生成新的绕过 Cookie 值。

这确保了绕过 Cookie 无法被猜测。

须知:要通过 HTTP 在本地测试草稿模式,浏览器需允许第三方 Cookie 和本地存储访问。

On this page