Skip to main content

草稿模式

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

¥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 view 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 Draft Mode which solves this problem. Here are instructions on how to use it.

步骤 1:创建并访问 API 路由

¥Step 1: Create and access the 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/draft.ts

¥First, create the API route. It can have any name - e.g. pages/api/draft.ts

在此 API 路由中,你需要在响应对象上调用 setDraftMode

¥In this API route, you need to call setDraftMode on the response object.

export default function handler(req, res) {
// ...
res.setDraftMode({ enable: true })
// ...
}

这将设置一个 cookie 以启用草稿模式。包含此 cookie 的后续请求将触发草稿模式,更改静态生成页面的行为(稍后会详细介绍)。

¥This will set a cookie to enable draft mode. Subsequent requests containing this cookie will trigger Draft Mode changing the behavior for statically generated pages (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.setDraftMode({ enable: true })
res.end('Draft mode is enabled')
}

如果你打开浏览器的开发者工具并访问 /api/draft,你会注意到 Set-Cookie 响应标头以及名为 __prerender_bypass 的 cookie。

¥If you open your browser’s developer tools and visit /api/draft, you’ll notice a Set-Cookie response header with a cookie named __prerender_bypass.

从 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 draft URLs. If it doesn’t, you can still use this method to secure your draft URLs, but you’ll need to construct and access the draft 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 draft URLs.

其次,如果你的无头 CMS 支持设置自定义草稿 URL,请指定以下内容作为草稿 URL。这假设你的草案 API 路由位于 pages/api/draft.ts

¥Second, if your headless CMS supports setting custom draft URLs, specify the following as the draft URL. This assumes that your draft API route is located at pages/api/draft.ts.

https://<your-site>/api/draft?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 view. If you want to view /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 draft 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 draft API route:

  • 检查密钥是否匹配以及 slug 参数是否存在(如果不存在,则请求应失败)。

    ¥Check that the secret matches and that the slug parameter exists (if not, the request should fail).

    • 致电 res.setDraftMode

    ¥Call res.setDraftMode.

  • 然后将浏览器重定向到 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 draft mode from being enabled
if (!post) {
return res.status(401).json({ message: 'Invalid slug' })
}

// Enable Draft Mode by setting the cookie
res.setDraftMode({ enable: true })

// 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 view with the draft mode cookie.

步骤 2:更新 getStaticProps

¥Step 2: Update getStaticProps

下一步是更新 getStaticProps 以支持草稿模式。

¥The next step is to update getStaticProps to support draft mode.

如果你请求一个具有 getStaticProps 且设置了 cookie 的页面(通过 res.setDraftMode),那么 getStaticProps 将在请求时(而不是在构建时)被调用。

¥If you request a page which has getStaticProps with the cookie set (via res.setDraftMode), then getStaticProps will be called at request time (instead of at build time).

此外,它将使用 context 对象调用,其中 context.draftMode 将是 true

¥Furthermore, it will be called with a context object where context.draftMode will be true.

export async function getStaticProps(context) {
if (context.draftMode) {
// dynamic data
}
}

我们在 API 路由草案中使用了 res.setDraftMode,因此 context.draftMode 将是 true

¥We used res.setDraftMode in the draft API route, so context.draftMode will be true.

如果你也使用 getStaticPaths,那么 context.params 也可用。

¥If you’re also using getStaticPaths, then context.params will also be available.

获取草稿数据

¥Fetch draft data

你可以更新 getStaticProps 以基于 context.draftMode 获取不同的数据。

¥You can update getStaticProps to fetch different data based on context.draftMode.

例如,你的无头 CMS 可能对草稿帖子有不同的 API 端点。如果是这样,你可以修改 API 端点 URL,如下所示:

¥For example, your headless CMS might have a different API endpoint for draft posts. If so, you can modify the API endpoint URL like below:

export async function getStaticProps(context) {
const url = context.draftMode
? 'https://draft.example.com'
: 'https://production.example.com'
const res = await fetch(url)
// ...
}

就是这样!如果你从无头 CMS 或手动访问草稿 API 路由(使用 secretslug),你现在应该能够看到草稿内容。如果你更新草稿而不发布,你应该能够查看该草稿。

¥That’s it! If you access the draft API route (with secret and slug) from your headless CMS or manually, you should now be able to see the draft content. And if you update your draft without publishing, you should be able to view the draft.

将其设置为无头 CMS 上的草稿 URL 或手动访问,你应该能够看到草稿。

¥Set this as the draft URL on your headless CMS or access manually, and you should be able to see the draft.

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

更多细节

¥More Details

¥Clear the Draft Mode cookie

默认情况下,草稿模式会话在浏览器关闭时结束。

¥By default, the Draft Mode session ends when the browser is closed.

要手动清除草稿模式 cookie,请创建调用 setDraftMode({ enable: false }) 的 API 路由:

¥To clear the Draft Mode cookie manually, create an API route that calls setDraftMode({ enable: false }):

export default function handler(req, res) {
res.setDraftMode({ enable: false })
}

然后,向 /api/disable-draft 发送调用 API 路由的请求。如果使用 next/link 调用此路由,则必须传递 prefetch={false} 以防止在预取时意外删除 cookie。

¥Then, send a request to /api/disable-draft to invoke the API Route. If calling this route using next/link, you must pass prefetch={false} to prevent accidentally deleting the cookie on prefetch.

getServerSideProps 合作

¥Works with getServerSideProps

草图模式与 getServerSideProps 配合使用,并且可用作 context 对象中的 draftMode 键。

¥Draft Mode works with getServerSideProps, and is available as a draftMode key in the context object.

很高兴知道:使用草稿模式时不应设置 Cache-Control 标头,因为它无法被绕过。相反,我们建议使用 ISR

¥Good to know: You shouldn't set the Cache-Control header when using Draft Mode because it cannot be bypassed. Instead, we recommend using ISR.

使用 API 路由

¥Works with API Routes

API 路由将有权访问请求对象上的 draftMode。例如:

¥API Routes will have access to draftMode on the request object. For example:

export default function myApiRoute(req, res) {
if (req.draftMode) {
// get draft data
}
}

每个 next build 都是唯一的

¥Unique per next build

每次运行 next build 时都会生成一个新的旁路 cookie 值。

¥A new bypass cookie value will be generated each time you run next build.

这确保了旁路 cookie 无法被猜测。

¥This ensures that the bypass cookie can’t be guessed.

很高兴知道:要通过 HTTP 在本地测试草稿模式,你的浏览器需要允许第三方 cookie 和本地存储访问。

¥Good to know: To test Draft Mode locally over HTTP, your browser will need to allow third-party cookies and local storage access.