如何在 Next.js 中使用预览模式查看内容

注意:此功能已被 草稿模式 (Draft Mode) 取代。

示例

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

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

Next.js 提供了称为预览模式 (Preview Mode) 的功能来解决此问题。以下是使用方法的说明。

第一步:创建并访问预览 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 的 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

终端
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) => {
  // 检查秘密和下一个参数
  // 此秘密应仅由此 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。

第二步:更新 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 或手动访问,您应该能够看到预览。

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

更多详情

须知:在渲染期间,next/router 会公开一个 isPreview 标志,有关更多信息,请参阅 路由器对象文档

指定预览模式的持续时间

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。它也将出现在包含 previewpreviewDatacontext 对象上。

须知:使用预览模式时不应设置 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 和本地存储访问。