草稿模式 (Draft Mode)

当页面从无头 CMS (headless CMS) 获取数据时,静态渲染非常有用。但若您在无头 CMS 上撰写草稿并希望立即在页面上预览时,静态渲染就不太理想。此时您会希望 Next.js 在请求时而非构建时渲染这些页面,并获取草稿内容而非已发布内容。您会希望 Next.js 仅针对这种情况切换到动态渲染

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

步骤 1:创建并访问路由处理器 (Route Handler)

首先创建一个路由处理器。命名不限,例如 app/api/draft/route.ts

然后从 next/headers 导入 draftMode 并调用其 enable() 方法。

// 启用草稿模式的路由处理器
import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().enable()
  return new Response('草稿模式已启用')
}

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

您可通过访问 /api/draft 并查看浏览器开发者工具来手动测试。注意响应头中的 Set-Cookie 会设置名为 __prerender_bypass 的 Cookie。

从无头 CMS 安全访问

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

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

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

其次,若 CMS 支持自定义草稿 URL,请指定以下格式作为草稿 URL(假设路由处理器位于 app/api/draft/route.ts):

Terminal
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}

最后,在路由处理器中:

  • 检查密钥是否匹配及 slug 参数是否存在(否则请求应失败)
  • 调用 draftMode.enable() 设置 Cookie
  • 将浏览器重定向到 slug 指定路径
// 带密钥和 slug 的路由处理器
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  // 解析查询字符串参数
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  // 验证密钥和必要参数
  // 该密钥应仅由此路由处理器和 CMS 知晓
  if (secret !== 'MY_SECRET_TOKEN' || !slug) {
    return new Response('无效令牌', { status: 401 })
  }

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

  // 若 slug 不存在则阻止启用草稿模式
  if (!post) {
    return new Response('无效路径', { status: 401 })
  }

  // 通过设置 Cookie 启用草稿模式
  draftMode().enable()

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

若操作成功,浏览器将携带草稿模式 Cookie 重定向至目标路径。

步骤 2:更新页面

下一步是更新页面以检查 draftMode().isEnabled 的值。

若请求的页面携带了该 Cookie,数据将在请求时获取(而非构建时)。

此外,isEnabled 的值将为 true

// 获取数据的页面
import { draftMode } from 'next/headers'

async function getData() {
  const { isEnabled } = draftMode()

  const url = isEnabled
    ? 'https://draft.example.com'
    : 'https://production.example.com'

  const res = await fetch(url)

  return res.json()
}

export default async function Page() {
  const { title, desc } = await getData()

  return (
    <main>
      <h1>{title}</h1>
      <p>{desc}</p>
    </main>
  )
}

至此完成!若从无头 CMS 或手动访问草稿路由处理器(携带 secretslug),您现在应能看到草稿内容。当您更新草稿但未发布时,也能立即查看变更。

在无头 CMS 中设置此草稿 URL 或手动访问,即可预览草稿:

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

更多细节

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

要手动清除草稿模式 Cookie,可创建调用 draftMode().disable() 的路由处理器:

import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().disable()
  return new Response('草稿模式已禁用')
}

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

每次 next build 生成唯一值

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

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

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

On this page