Menu

预览模式

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

示例

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

静态生成对于从无头 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 在浏览器上设置一些Cookie,从而开启预览模式。任何包含这些 Cookie 的 Next.js 请求都将被视为预览模式,并且静态生成页面的行为将发生变化(稍后会详细说明)。

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

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

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

从无头 CMS 安全地访问

实际上,你希望从无头 CMS 安全地调用此 API 路由。具体步骤将取决于你使用的无头 CMS,但以下是一些常见步骤。

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

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

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

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

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

最后,在预览 API 路由中:

  • 检查秘密是否匹配,并且 slug 参数是否存在(如果不存在,请求应该失败)。
  • 调用 res.setPreviewData
  • 然后将浏览器重定向到 slug 指定的路径。(下面的示例使用 307 重定向)。
export default async (req, res) => {
  // 检查秘密和 next 参数
  // 此秘密应该只有此 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.setPreviewData({})
 
  // 重定向到获取的文章的路径
  // 我们不重定向到 req.query.slug,因为这可能导致开放重定向漏洞
  res.redirect(post.slug)
}

如果成功,浏览器将被重定向到你想预览的路径,并设置预览模式 Cookie。

步骤 2:更新 getStaticProps

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

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

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

  • context.preview 将为 true
  • context.previewData 将与 setPreviewData 的参数相同。
export async function getStaticProps(context) {
  // 如果使用预览模式 Cookie 请求此页面:
  //
  // - 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://<你的站点>/api/preview?secret=<令牌>&slug=<路径>

更多细节

值得注意的是:在渲染期间,next/router 会暴露一个 isPreview 标志,请参阅 路由器对象文档 了解更多信息。

指定预览模式持续时间

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

  • maxAge:指定预览会话持续的秒数。
  • path:指定应用 Cookie 的路径。默认为 /,为所有路径启用预览模式。
setPreviewData(data, {
  maxAge: 60 * 60, // 预览模式 Cookie 在 1 小时后过期
  path: '/about', // 预览模式 Cookie 应用于 /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。它将在包含 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 测试预览模式,你的浏览器需要允许第三方 Cookies 和本地存储访问。