预览模式
注意:此功能已被 草稿模式 取代。
¥Note: This feature is superseded by Draft Mode.
Examples
在 页面文档 和 数据获取文档 中,我们讨论了如何使用 getStaticProps
和 getStaticPaths
在构建时(静态生成)预渲染页面。
¥In the Pages documentation and the Data Fetching documentation, we talked about how to pre-render a page at build time (Static Generation) using getStaticProps
and getStaticPaths
.
当你的页面从无头 CMS 获取数据时,静态生成非常有用。但是,当你在无头 CMS 上编写草稿并希望立即在页面上预览草稿时,这并不理想。你希望 Next.js 在请求时而不是构建时渲染这些页面,并获取草稿内容而不是已发布的内容。你希望 Next.js 仅在这种特定情况下绕过静态生成。
¥Static Generation is useful when your pages fetch data from a headless CMS. However, it’s not ideal when you’re writing a draft on your headless CMS and want to preview the draft immediately on your page. You’d want Next.js to render these pages at request time instead of build time and fetch the draft content instead of the published content. You’d want Next.js to bypass Static Generation only for this specific case.
Next.js 有一个称为预览模式的功能可以解决这个问题。以下是有关如何使用它的说明。
¥Next.js has a feature called Preview Mode which solves this problem. Here are instructions on how to use it.
步骤 1:创建并访问预览 API 路由
¥Step 1: Create and access a preview API route
如果你不熟悉 Next.js API 路由,请先查看 API 路由文档。
¥Take a look at the API Routes documentation first if you’re not familiar with Next.js API Routes.
首先,创建预览 API 路由。它可以有任何名称 - 例如 pages/api/preview.js
(如果使用 TypeScript,则为 .ts
)。
¥First, create a preview API route. It can have any name - e.g. pages/api/preview.js
(or .ts
if using TypeScript).
在此 API 路由中,你需要在响应对象上调用 setPreviewData
。setPreviewData
的参数应该是一个对象,并且 getStaticProps
可以使用它(稍后会详细介绍)。现在,我们将使用 {}
。
¥In this API route, you need to call setPreviewData
on the response object. The argument for setPreviewData
should be an object, and this can be used by getStaticProps
(more on this later). For now, we’ll use {}
.
export default function handler(req, res) {
// ...
res.setPreviewData({})
// ...
}
res.setPreviewData
在浏览器上设置一些 cookie,以打开预览模式。对包含这些 cookie 的 Next.js 的任何请求都将被视为预览模式,并且静态生成页面的行为将发生变化(稍后会详细介绍)。
¥res.setPreviewData
sets some cookies on the browser which turns on the preview mode. Any requests to Next.js containing these cookies will be considered as the preview mode, and the behavior for statically generated pages will change (more on this later).
你可以通过创建如下所示的 API 路由并从浏览器手动访问它来手动测试:
¥You can test this manually by creating an API route like below and accessing it from your browser manually:
// simple example for testing it manually from your browser.
export default function handler(req, res) {
res.setPreviewData({})
res.end('Preview mode enabled')
}
如果你打开浏览器的开发者工具并访问 /api/preview
,你会注意到将根据此请求设置 __prerender_bypass
和 __next_preview_data
cookie。
¥If you open your browser’s developer tools and visit /api/preview
, you’ll notice that the __prerender_bypass
and __next_preview_data
cookies will be set on this request.
从 Headless CMS 安全地访问它
¥Securely accessing it from your Headless CMS
在实践中,你希望从无头 CMS 安全地调用此 API 路由。具体步骤将根据你使用的无头 CMS 的不同而有所不同,但以下是你可以采取的一些常见步骤。
¥In practice, you’d want to call this API route securely from your headless CMS. The specific steps will vary depending on which headless CMS you’re using, but here are some common steps you could take.
这些步骤假设你使用的无头 CMS 支持设置自定义预览 URL。如果没有,你仍然可以使用此方法来保护预览 URL,但你需要手动构建和访问预览 URL。
¥These steps assume that the headless CMS you’re using supports setting custom preview URLs. If it doesn’t, you can still use this method to secure your preview URLs, but you’ll need to construct and access the preview URL manually.
首先,你应该使用你选择的令牌生成器创建一个秘密令牌字符串。这个秘密只有你的 Next.js 应用和无头 CMS 知道。此密钥可防止无权访问你的 CMS 的人员访问预览 URL。
¥First, you should create a secret token string using a token generator of your choice. This secret will only be known by your Next.js app and your headless CMS. This secret prevents people who don’t have access to your CMS from accessing preview URLs.
其次,如果你的无头 CMS 支持设置自定义预览 URL,请指定以下内容作为预览 URL。这假设你的预览 API 路由位于 pages/api/preview.js
。
¥Second, if your headless CMS supports setting custom preview URLs, specify the following as the preview URL. This assumes that your preview API route is located at pages/api/preview.js
.
https://<your-site>/api/preview?secret=<token>&slug=<path>
-
<your-site>
应该是你的部署域。¥
<your-site>
should be your deployment domain. -
<token>
应替换为你生成的秘密令牌。¥
<token>
should be replaced with the secret token you generated. -
<path>
应该是你要预览的页面的路径。如果你想预览/posts/foo
,那么你应该使用&slug=/posts/foo
。¥
<path>
should be the path for the page that you want to preview. If you want to preview/posts/foo
, then you should use&slug=/posts/foo
.
你的无头 CMS 可能允许你在预览 URL 中包含一个变量,以便可以根据 CMS 的数据动态设置 <path>
,如下所示:&slug=/posts/{entry.fields.slug}
¥Your headless CMS might allow you to include a variable in the preview URL so that <path>
can be set dynamically based on the CMS’s data like so: &slug=/posts/{entry.fields.slug}
最后,在预览 API 路由中:
¥Finally, in the preview API route:
-
检查密钥是否匹配以及
slug
参数是否存在(如果不存在,则请求应失败)。¥Check that the secret matches and that the
slug
parameter exists (if not, the request should fail). -
- 致电
res.setPreviewData
。
¥Call
res.setPreviewData
. - 致电
-
然后将浏览器重定向到
slug
指定的路径。(以下示例使用 307 重定向)。¥Then redirect the browser to the path specified by
slug
. (The following example uses a 307 redirect).
export default async (req, res) => {
// Check the secret and next parameters
// This secret should only be known to this API route and the CMS
if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
return res.status(401).json({ message: 'Invalid token' })
}
// Fetch the headless CMS to check if the provided `slug` exists
// getPostBySlug would implement the required fetching logic to the headless CMS
const post = await getPostBySlug(req.query.slug)
// If the slug doesn't exist prevent preview mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Invalid slug' })
}
// Enable Preview Mode by setting the cookies
res.setPreviewData({})
// Redirect to the path from the fetched post
// We don't redirect to req.query.slug as that might lead to open redirect vulnerabilities
res.redirect(post.slug)
}
如果成功,浏览器将被重定向到你要预览的路径,并设置预览模式 cookie。
¥If it succeeds, then the browser will be redirected to the path you want to preview with the preview mode cookies being set.
步骤 2:更新 getStaticProps
¥Step 2: Update getStaticProps
下一步是更新 getStaticProps
以支持预览模式。
¥The next step is to update getStaticProps
to support the preview mode.
如果你请求一个具有 getStaticProps
并设置了预览模式 cookie 的页面(通过 res.setPreviewData
),那么 getStaticProps
将在请求时(而不是在构建时)被调用。
¥If you request a page which has getStaticProps
with the preview mode cookies set (via res.setPreviewData
), then getStaticProps
will be called at request time (instead of at build time).
此外,它将使用 context
对象调用,其中:
¥Furthermore, it will be called with a context
object where:
-
context.preview
将是true
。¥
context.preview
will betrue
. -
context.previewData
将与setPreviewData
使用的参数相同。¥
context.previewData
will be the same as the argument used forsetPreviewData
.
export async function getStaticProps(context) {
// If you request this page with the preview mode cookies set:
//
// - context.preview will be true
// - context.previewData will be the same as
// the argument used for `setPreviewData`.
}
我们在预览 API 路由中使用了 res.setPreviewData({})
,因此 context.previewData
将是 {}
。如有必要,你可以使用它将会话信息从预览 API 路由传递到 getStaticProps
。
¥We used res.setPreviewData({})
in the preview API route, so context.previewData
will be {}
. You can use this to pass session information from the preview API route to getStaticProps
if necessary.
如果你也使用 getStaticPaths
,那么 context.params
也可用。
¥If you’re also using getStaticPaths
, then context.params
will also be available.
获取预览数据
¥Fetch preview data
你可以更新 getStaticProps
以基于 context.preview
和/或 context.previewData
获取不同的数据。
¥You can update getStaticProps
to fetch different data based on context.preview
and/or context.previewData
.
例如,你的无头 CMS 可能对草稿帖子有不同的 API 端点。如果是这样,你可以使用 context.preview
修改 API 端点 URL,如下所示:
¥For example, your headless CMS might have a different API endpoint for draft posts. If so, you can use context.preview
to modify the API endpoint URL like below:
export async function getStaticProps(context) {
// If context.preview is true, append "/preview" to the API endpoint
// to request draft data instead of published data. This will vary
// based on which headless CMS you're using.
const res = await fetch(`https://.../${context.preview ? 'preview' : ''}`)
// ...
}
就是这样!如果你从无头 CMS 或手动访问预览 API 路由(使用 secret
和 slug
),你现在应该能够看到预览内容。如果你更新草稿而不发布,你应该能够预览草稿。
¥That’s it! If you access the preview API route (with secret
and slug
) from your headless CMS or manually, you should now be able to see the preview content. And if you update your draft without publishing, you should be able to preview the draft.
将其设置为无头 CMS 上的预览 URL 或手动访问,你应该能够看到预览。
¥Set this as the preview URL on your headless CMS or access manually, and you should be able to see the preview.
https://<your-site>/api/preview?secret=<token>&slug=<path>
更多细节
¥More Details
很高兴知道:在渲染期间
next/router
会暴露isPreview
标志,请参阅 路由对象文档 了解更多信息。¥Good to know: during rendering
next/router
exposes anisPreview
flag, see the router object docs for more info.
指定预览模式持续时间
¥Specify the Preview Mode duration
setPreviewData
采用可选的第二个参数,该参数应该是一个选项对象。它接受以下键:
¥setPreviewData
takes an optional second parameter which should be an options object. It accepts the following keys:
-
maxAge
:指定预览会话持续的时间(以秒为单位)。¥
maxAge
: Specifies the number (in seconds) for the preview session to last for. -
path
:指定应应用 cookie 的路径。默认为/
,为所有路径启用预览模式。¥
path
: Specifies the path the cookie should be applied under. Defaults to/
enabling preview mode for all paths.
setPreviewData(data, {
maxAge: 60 * 60, // The preview mode cookies expire in 1 hour
path: '/about', // The preview mode cookies apply to paths with /about
})
清除预览模式 cookie
¥Clear the Preview Mode cookies
默认情况下,预览模式 cookie 没有设置过期日期,因此预览会话会在浏览器关闭时结束。
¥By default, no expiration date is set for Preview Mode cookies, so the preview session ends when the browser is closed.
要手动清除预览模式 cookie,请创建调用 clearPreviewData()
的 API 路由:
¥To clear the Preview Mode cookies manually, create an API route that calls clearPreviewData()
:
export default function handler(req, res) {
res.clearPreviewData({})
}
然后,向 /api/clear-preview-mode-cookies
发送调用 API 路由的请求。如果使用 next/link
调用该路由,则必须传递 prefetch={false}
,以防止在链接预取期间调用 clearPreviewData
。
¥Then, send a request to /api/clear-preview-mode-cookies
to invoke the API Route. If calling this route using next/link
, you must pass prefetch={false}
to prevent calling clearPreviewData
during link prefetching.
如果在 setPreviewData
调用中指定了路径,则必须将相同的路径传递给 clearPreviewData
:
¥If a path was specified in the setPreviewData
call, you must pass the same path to clearPreviewData
:
export default function handler(req, res) {
const { path } = req.query
res.clearPreviewData({ path })
}
previewData
尺寸限制
¥previewData
size limits
你可以将对象传递给 setPreviewData
并使其在 getStaticProps
中可用。但是,由于数据将存储在 cookie 中,因此存在大小限制。目前,预览数据限制为 2KB。
¥You can pass an object to setPreviewData
and have it be available in getStaticProps
. However, because the data will be stored in a cookie, there’s a size limitation. Currently, preview data is limited to 2KB.
与 getServerSideProps
合作
¥Works with getServerSideProps
预览模式也适用于 getServerSideProps
。它还可用于包含 preview
和 previewData
的 context
对象。
¥The preview mode works on getServerSideProps
as well. It will also be available on the context
object containing preview
and previewData
.
很高兴知道:使用预览模式时不应设置
Cache-Control
标头,因为它无法被绕过。相反,我们建议使用 ISR。¥Good to know: You shouldn't set the
Cache-Control
header when using Preview Mode because it cannot be bypassed. Instead, we recommend using ISR.
使用 API 路由
¥Works with API Routes
API 路由将有权访问请求对象下的 preview
和 previewData
。例如:
¥API Routes will have access to preview
and previewData
under the request object. For example:
export default function myApiRoute(req, res) {
const isPreview = req.preview
const previewData = req.previewData
// ...
}
每个 next build
都是唯一的
¥Unique per next build
当 next build
完成时,旁路 cookie 值和用于加密 previewData
的私钥都会发生变化。这确保了旁路 cookie 无法被猜测。
¥Both the bypass cookie value and the private key for encrypting the previewData
change when next build
is completed.
This ensures that the bypass cookie can’t be guessed.
很高兴知道:要通过 HTTP 在本地测试预览模式,你的浏览器需要允许第三方 cookie 和本地存储访问。
¥Good to know: To test Preview Mode locally over HTTP your browser will need to allow third-party cookies and local storage access.