Menu

How to preview content with Draft Mode in Next.js

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

当你的页面从 headless CMS 获取数据时,静态生成非常有用。然而,当你在 headless CMS 中编写草稿并希望立即在页面上查看草稿时,这并不理想。你希望 Next.js 在请求时而非构建时渲染这些页面,并获取草稿内容而非已发布内容。你希望 Next.js 仅在这种特定情况下绕过静态生成。

Next.js 有一个名为草稿模式的功能可以解决这个问题。以下是如何使用它的说明。

步骤 1:创建并访问 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('Draft mode is enabled')
}

如果你打开浏览器的开发者工具并访问 /api/draft,你会注意到一个带有名为 __prerender_bypass 的 cookie 的 Set-Cookie 响应头。

从你的 Headless CMS 安全地访问它

实际上,你会希望从你的 headless CMS _安全地_调用这个 API 路由。具体步骤将取决于你使用的 headless CMS,但这里有一些你可以采取的常见步骤。

这些步骤假设你使用的 headless CMS 支持设置自定义草稿 URL。如果不支持,你仍然可以使用此方法来保护你的草稿 URL,但你需要手动构建和访问草稿 URL。

首先,你应该使用你选择的令牌生成器创建一个秘密令牌字符串。这个秘密只会被你的 Next.js 应用和你的 headless CMS 知道。这个秘密防止没有访问你的 CMS 权限的人访问草稿 URL。

其次,如果你的 headless CMS 支持设置自定义草稿 URL,请将以下内容指定为草稿 URL。这假设你的草稿 API 路由位于 pages/api/draft.ts

Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site> 应该是你的部署域名。
  • <token> 应该替换为你生成的秘密令牌。
  • <path> 应该是你想要查看的页面的路径。如果你想查看 /posts/foo,那么你应该使用 &slug=/posts/foo

你的 headless CMS 可能允许你在草稿 URL 中包含一个变量,以便 <path> 可以根据 CMS 的数据动态设置,如:&slug=/posts/{entry.fields.slug}

最后,在草稿 API 路由中:

  • 检查秘密是否匹配以及 slug 参数是否存在(如果不存在,请求应该失败)。
  • 调用 res.setDraftMode
  • 然后将浏览器重定向到 slug 指定的路径。(以下示例使用 307 重定向)。
export default async (req, res) => {
  // 检查 secret 和 next 参数
  // 这个 secret 应该只有这个 API 路由和 CMS 知道
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  // 获取 headless CMS 以检查提供的 `slug` 是否存在
  // getPostBySlug 将实现向 headless CMS 获取数据的必要逻辑
  const post = await getPostBySlug(req.query.slug)
 
  // 如果 slug 不存在,防止启用草稿模式
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }
 
  // 通过设置 cookie 启用草稿模式
  res.setDraftMode({ enable: true })
 
  // 重定向到从获取的帖子中的路径
  // 我们不重定向到 req.query.slug,因为这可能导致开放重定向漏洞
  res.redirect(post.slug)
}

如果成功,浏览器将被重定向到你想要查看的路径,并带有草稿模式 cookie。

步骤 2:更新 getStaticProps

下一步是更新 getStaticProps 以支持草稿模式。

如果你请求一个带有 getStaticProps 的页面,并且设置了 cookie(通过 res.setDraftMode),那么 getStaticProps 将在请求时被调用(而不是在构建时)。

此外,它将被调用时带有一个 context 对象,其中 context.draftMode 将为 true

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

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

如果你也使用 getStaticPaths,那么 context.params 也将可用。

获取草稿数据

你可以更新 getStaticProps 以根据 context.draftMode 获取不同的数据。

例如,你的 headless CMS 可能有一个用于草稿帖子的不同 API 端点。如果是这样,你可以像下面这样修改 API 端点 URL:

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

就是这样!如果你从你的 headless CMS 或手动访问草稿 API 路由(带有 secretslug),你现在应该能够看到草稿内容。如果你更新你的草稿而不发布,你应该能够查看草稿。

在你的 headless CMS 上设置这个作为草稿 URL 或手动访问,你应该能够看到草稿。

Terminal
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 来调用 API 路由。如果使用 next/link 调用此路由,你必须传递 prefetch={false} 以防止在预取时意外删除 cookie。

getServerSideProps 一起工作

草稿模式与 getServerSideProps 一起工作,并在 context 对象中作为 draftMode 键可用。

值得注意的是:使用草稿模式时不应设置 Cache-Control 头,因为它无法被绕过。相反,我们建议使用 ISR

与 API 路由一起工作

API 路由将在请求对象上访问 draftMode。例如:

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

每次 next build 都是唯一的

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

这确保绕过 cookie 不能被猜测。

值得注意的是:要通过 HTTP 在本地测试草稿模式,你的浏览器需要允许第三方 cookie 和本地存储访问。