Menu

How to preview content with Preview Mode in Next.js

注意:此功能已被草稿模式取代。

示例

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

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

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

步骤 1:创建并访问预览 API 路由

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

首先,创建一个预览 API 路由。它可以有任何名称 - 例如 pages/api/preview.js(如果使用 TypeScript,则为 .ts)。

在这个 API 路由中,你需要在响应对象上调用 setPreviewDatasetPreviewData 的参数应该是一个对象,这可以被 getStaticProps 使用(稍后会详细说明)。现在,我们将使用 {}

export default function handler(req, res) {
  // ...
  res.setPreviewData({})
  // ...
}

res.setPreviewData 在浏览器上设置一些cookies,这些 cookies 会开启预览模式。任何包含这些 cookies 的对 Next.js 的请求都将被视为预览模式,并且静态生成页面的行为将会改变(稍后会详细说明)。

你可以通过创建如下的 API 路由并从浏览器手动访问它来测试这个功能:

pages/api/preview.js
// 用于从浏览器手动测试的简单示例
export default function handler(req, res) {
  res.setPreviewData({})
  res.end('预览模式已启用')
}

如果你打开浏览器的开发者工具并访问 /api/preview,你会注意到在这个请求中设置了 __prerender_bypass__next_preview_data cookies。

从你的无头 CMS 安全地访问它

在实践中,你会希望从你的无头 CMS 安全地调用这个 API 路由。具体步骤将取决于你使用的无头 CMS,但以下是一些可以采取的常见步骤。

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

首先,你应该使用你选择的令牌生成器创建一个秘密令牌字符串。这个秘密将只有你的 Next.js 应用和你的无头 CMS 知道。这个秘密可以防止没有访问你的 CMS 权限的人访问预览 URL。

第二,如果你的无头 CMS 支持设置自定义预览 URL,请指定以下内容作为预览 URL。这假设你的预览 API 路由位于 pages/api/preview.js

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

你的无头 CMS 可能允许你在预览 URL 中包含一个变量,这样 <path> 可以根据 CMS 的数据动态设置,例如:&slug=/posts/{entry.fields.slug}

最后,在预览 API 路由中:

  • 检查秘密是否匹配以及 slug 参数是否存在(如果不存在,请求应该失败)。
  • 调用 res.setPreviewData
  • 然后将浏览器重定向到由 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: '无效的令牌' })
  }
 
  // 获取无头 CMS 数据以检查提供的 `slug` 是否存在
  // getPostBySlug 将实现所需的获取无头 CMS 的逻辑
  const post = await getPostBySlug(req.query.slug)
 
  // 如果 slug 不存在,阻止启用预览模式
  if (!post) {
    return res.status(401).json({ message: '无效的 slug' })
  }
 
  // 通过设置 cookies 启用预览模式
  res.setPreviewData({})
 
  // 重定向到从获取的文章中的路径
  // 我们不重定向到 req.query.slug,因为这可能会导致开放重定向漏洞
  res.redirect(post.slug)
}

如果成功,浏览器将重定向到你想要预览的路径,同时设置预览模式 cookies。

步骤 2:更新 getStaticProps

下一步是更新 getStaticProps 以支持预览模式。

如果你请求一个具有 getStaticProps 的页面,并且设置了预览模式 cookies(通过 res.setPreviewData),那么 getStaticProps 将在请求时被调用(而不是在构建时)。

此外,它将使用一个 context 对象被调用,其中:

  • context.preview 将为 true
  • context.previewData 将与用于 setPreviewData 的参数相同。
export async function getStaticProps(context) {
  // 如果你使用预览模式 cookies 请求此页面:
  //
  // - context.preview 将为 true
  // - context.previewData 将与用于 `setPreviewData` 的参数相同。
}

我们在预览 API 路由中使用了 res.setPreviewData({}),所以 context.previewData 将为 {}。如果需要,你可以使用它将会话信息从预览 API 路由传递到 getStaticProps

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

获取预览数据

你可以更新 getStaticProps 来基于 context.preview 和/或 context.previewData 获取不同的数据。

例如,你的无头 CMS 可能有一个针对草稿文章的不同 API 端点。如果是这样,你可以使用 context.preview 修改 API 端点 URL,如下所示:

export async function getStaticProps(context) {
  // 如果 context.preview 为 true,在 API 端点后附加 "/preview"
  // 以请求草稿数据而不是已发布数据。这将根据你使用的无头 CMS 而有所不同。
  const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
  // ...
}

就是这样!如果你从你的无头 CMS 或手动访问预览 API 路由(带有 secretslug),你现在应该能够看到预览内容了。如果你更新了草稿但未发布,你应该能够预览草稿。

在你的无头 CMS 上设置这个作为预览 URL 或手动访问,你应该能够看到预览。

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

更多细节

值得注意的是:在渲染期间,next/router 暴露了一个 isPreview 标志,详见 router 对象文档

指定预览模式持续时间

setPreviewData 接受一个可选的第二个参数,它应该是一个选项对象。它接受以下键:

  • maxAge:指定预览会话持续的秒数。
  • path:指定 cookie 应该应用的路径。默认为 /,为所有路径启用预览模式。
setPreviewData(data, {
  maxAge: 60 * 60, // 预览模式 cookies 在 1 小时后过期
  path: '/about', // 预览模式 cookies 应用于带有 /about 的路径
})

清除预览模式 cookies

默认情况下,预览模式 cookies 没有设置过期日期,因此预览会话在浏览器关闭时结束。

要手动清除预览模式 cookies,创建一个调用 clearPreviewData() 的 API 路由:

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  res.clearPreviewData({})
}

然后,发送一个请求到 /api/clear-preview-mode-cookies 来调用 API 路由。如果使用 next/link 调用此路由,你必须传递 prefetch={false} 以防止在链接预取期间调用 clearPreviewData

如果在 setPreviewData 调用中指定了路径,你必须将相同的路径传递给 clearPreviewData

pages/api/clear-preview-mode-cookies.js
export default function handler(req, res) {
  const { path } = req.query
 
  res.clearPreviewData({ path })
}

previewData 大小限制

你可以将一个对象传递给 setPreviewData 并在 getStaticProps 中使用它。然而,由于数据将存储在 cookie 中,所以有大小限制。目前,预览数据限制为 2KB。

适用于 getServerSideProps

预览模式也适用于 getServerSideProps。它也会在 context 对象中提供 previewpreviewData

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

适用于 API 路由

API 路由将在请求对象下访问 previewpreviewData。例如:

export default function myApiRoute(req, res) {
  const isPreview = req.preview
  const previewData = req.previewData
  // ...
}

每次 next build 唯一

绕过 cookie 值和用于加密 previewData 的私钥在 next build 完成时会改变。 这确保了绕过 cookie 不能被猜测。

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