预览模式
注意:此功能已被 草稿模式 取代。
示例
- 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 示例 (演示)
- 博客起始示例 (演示)
在 页面文档 和 数据获取文档 中,我们讨论了如何使用 getStaticProps
和 getStaticPaths
在构建时预渲染页面(静态生成)。
静态生成对于从无头 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
在浏览器上设置一些Cookie,从而开启预览模式。任何包含这些 Cookie 的 Next.js 请求都将被视为预览模式,并且静态生成页面的行为将发生变化(稍后会详细说明)。
你可以通过创建如下 API 路由并从浏览器手动访问来手动测试:
// 用于从浏览器手动测试的简单示例。
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
。
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 路由(带有 secret
和 slug
),现在应该能够看到预览内容。如果更新草稿而不发布,你应该能够预览草稿。
在你的无头 CMS 上设置此预览 URL 或手动访问,你应该能够看到预览。
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 路由:
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
的上下文对象中可用。
值得注意的是:在使用预览模式时,你不应设置
Cache-Control
标头,因为它无法绕过。相反,我们建议使用 ISR。
与 API 路由兼容
API 路由将可以在请求对象下访问 preview
和 previewData
。例如:
export default function myApiRoute(req, res) {
const isPreview = req.preview
const previewData = req.previewData
// ...
}
每次 next build
唯一
绕过 Cookie 值和用于加密 previewData
的私钥在 next build
完成时都会改变。
这确保了绕过 Cookie 无法被猜测。
值得注意的是:要在本地通过 HTTP 测试预览模式,你的浏览器需要允许第三方 Cookies 和本地存储访问。