跳到主要内容

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

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

示例

页面文档数据获取文档中,我们讨论了如何使用 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 的对 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) => {
// 检查秘密和下一个参数
// 这个秘密应该只有这个 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 标志,有关更多信息,请参见路由器对象文档

指定预览模式持续时间

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 唯一

next build 完成时,绕过 cookie 值和用于加密 previewData 的私钥都会改变。 这确保绕过 cookie 无法被猜测。

提示:要在本地通过 HTTP 测试预览模式,你的浏览器需要允许第三方 cookies 和本地存储访问。