如何在 Next.js 中使用预览模式预览内容
注意:此功能已被草稿模式取代。
示例
- Agility CMS 示例 (演示)
- Builder.io 示例 (演示)
- ButterCMS 示例 (演示)
- Contentful 示例 (演示)
- Cosmic 示例 (演示)
- DatoCMS 示例 (演示)
- DotCMS 示例 (演示)
- Drupal 示例 (演示)
- Enterspeed 示例 (演示)
- GraphCMS 示例 (演示)
- Keystone 示例 (演示)
- Kontent.ai 示例 (演示)
- Makeswift 示例 (演示)
- Plasmic 示例 (演示)
- Prepr 示例 (演示)
- Prismic 示例 (演示)
- Sanity 示例 (演示)
- Sitecore XM Cloud 示例 (演示)
- Storyblok 示例 (演示)
- Strapi 示例 (演示)
- TakeShape 示例 (演示)
- Tina 示例 (演示)
- Umbraco 示例 (演示)
- Umbraco Heartcore 示例 (演示)
- Webiny 示例 (演示)
- WordPress 示例 (演示)
- Blog Starter 示例 (演示)
在页面文档和数据获取文档中,我们讨论了如何使用 getStaticProps
和 getStaticPaths
在构建时预渲染页面(静态生成)。
静态生成在页面从无头 CMS 获取数据时很有用。但是,当你在无头 CMS 上编写草稿并想要立即预览草稿时,这不是理想的。你希望 Next.js 在请求时而不是构建时渲染这些页面,并获取草稿内容而不是已发布的内容。你希望 Next.js 仅在这种情况下绕过静态生成。
Next.js 有一个称为预览模式的功能可以解决这个问题。以下是使用说明。
步骤 1:创建并访问预览 API 路由
如果你不熟悉 Next.js API 路由,请先查看 API 路由文档。
首先,创建一个预览 API 路由。它可以有任何名称 - 例如 pages/api/preview.js
(如果使用 TypeScript 则为 .ts
)。
在此 API 路由中,你需要在响应对象上调用 setPreviewData
。setPreviewData
的参数应该是一个对象,这可以被 getStaticProps
使用(稍后会详细介绍)。现在,我们将使用 {}
。
export default function handler(req, res) {
// ...
res.setPreviewData({})
// ...
}
res.setPreviewData
在浏览器上设置一些 cookies,这会打开预览模式。包含这些 cookies 的对 Next.js 的任何请求都将被视为预览模式,静态生成页面的行为将发生变化(稍后会详细介绍)。
你可以通过创建如下 API 路由并手动从浏览器访问它来手动测试:
// 用于从浏览器手动测试的简单示例。
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 将被设置。
步骤 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 路由(带有 secret
和 slug
),你现在应该能够看到预览内容。如果你更新草稿而不发布,你应该能够预览草稿。
在你的无头 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 路由:
export default function handler(req, res) {
res.clearPreviewData({})
}
然后,向 /api/clear-preview-mode-cookies
发送请求以调用 API 路由。如果使用 next/link
调用此路由,你必须传递 prefetch={false}
以防止在链接预取期间调用 clearPreviewData
。
如果在 setPreviewData
调用中指定了路径,你必须将相同的路径传递给 clearPreviewData
:
export default function handler(req, res) {
const { path } = req.query
res.clearPreviewData({ path })
}
previewData
大小限制
你可以向 setPreviewData
传递一个对象,并在 getStaticProps
中使用它。但是,由于数据将存储在 cookie 中,存在大小限制。目前,预览数据限制为 2KB。
与 getServerSideProps
一起工作
预览模式也适用于 getServerSideProps
。它也将在包含 preview
和 previewData
的 context
对象上可用。
提示:使用预览模式时不应该设置
Cache-Control
头,因为它无法被绕过。相反,我们建议使用 ISR。
与 API 路由一起工作
API 路由将在请求对象下访问 preview
和 previewData
。例如:
export default function myApiRoute(req, res) {
const isPreview = req.preview
const previewData = req.previewData
// ...
}
每个 next build
唯一
当 next build
完成时,绕过 cookie 值和用于加密 previewData
的私钥都会改变。
这确保绕过 cookie 无法被猜测。
提示:要在本地通过 HTTP 测试预览模式,你的浏览器需要允许第三方 cookies 和本地存储访问。