Skip to main content

草稿模式

静态渲染在从无头 CMS 获取数据时非常有用。然而,当您在无头 CMS 中撰写草稿并希望立即在页面上查看草稿时,静态渲染就不够理想了。在这种情况下,您希望 Next.js 在请求时渲染页面而不是在构建时渲染,并且获取草稿内容而不是已发布内容。为此,Next.js 可以仅在特定情况下切换到动态渲染

Next.js 提供了一个名为**草稿模式(Draft Mode)**的功能,解决了这个问题。以下是使用它的步骤:

第一步:创建并访问路由处理器

首先,创建一个路由处理器。它可以有任何名称,例如app/api/draft/route.ts

然后,从next/headers中导入draftMode,并调用 enable()方法。

// route handler enabling draft mode
import { draftMode } from "next/headers";

export async function GET(request: Request) {
draftMode().enable();
return new Response("Draft mode is enabled");
}
// route handler enabling draft mode
import { draftMode } from "next/headers";

export async function GET(request) {
draftMode().enable();
return new Response("Draft mode is enabled");
}

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

您可以通过访问/api/draft并查看浏览器开发工具手动测试此功能。注意Set-Cookie响应头,其中包含名为__prerender_bypass的 cookie。

从无头 CMS 安全访问它

在实际操作中,您会希望从无头 CMS 安全地 调用此路由处理器。具体步骤取决于您使用的无头 CMS,但以下是一些常见步骤。

假设您使用的无头 CMS 支持设置自定义草稿 URL。如果不支持,您仍然可以使用此方法来保护您的草稿 URL,但您需要手动构建和访问草稿 URL。

第一步,您应该使用您选择的令牌生成器创建一个密钥字符串。该密钥只能由您的 Next.js 应用和无头 CMS 知道,防止未授权人员访问草稿 URL。

第二步,如果您的无头 CMS 支持自定义草稿 URL,将以下 URL 作为草稿 URL。这假设您的路由处理器位于 app/api/draft/route.ts

https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site> 应替换为您部署的域名。
  • <token> 应替换为您生成的密钥。
  • <path> 应替换为您想要查看的页面路径。例如查看/posts/foo,则使用 &slug=/posts/foo

无头 CMS 可能允许您在草稿 URL 中包含变量,因此<path>可以根据 CMS 的数据动态设置,如:&slug=/posts/{entry.fields.slug}

最后,在路由处理器中:

  • 检查密钥是否匹配,并确保slug参数存在(否则请求应该失败)。
  • 调用draftMode.enable()以设置 cookie。
  • 然后将浏览器重定向到slug指定的路径。
// route handler with secret and slug
import { draftMode } from "next/headers";
import { redirect } from "next/navigation";

export async function GET(request: Request) {
// Parse query string parameters
const { searchParams } = new URL(request.url);
const secret = searchParams.get("secret");
const slug = searchParams.get("slug");

// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== "MY_SECRET_TOKEN" || !slug) {
return new Response("Invalid token", { status: 401 });
}

// 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(slug);

// If the slug doesn't exist prevent draft mode from being enabled
if (!post) {
return new Response("Invalid slug", { status: 401 });
}

// Enable Draft Mode by setting the cookie
draftMode().enable();

// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(post.slug);
}
// route handler with secret and slug
import { draftMode } from "next/headers";
import { redirect } from "next/navigation";

export async function GET(request) {
// Parse query string parameters
const { searchParams } = new URL(request.url);
const secret = searchParams.get("secret");
const slug = searchParams.get("slug");

// Check the secret and next parameters
// This secret should only be known to this route handler and the CMS
if (secret !== "MY_SECRET_TOKEN" || !slug) {
return new Response("Invalid token", { status: 401 });
}

// 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(slug);

// If the slug doesn't exist prevent draft mode from being enabled
if (!post) {
return new Response("Invalid slug", { status: 401 });
}

// Enable Draft Mode by setting the cookie
draftMode().enable();

// Redirect to the path from the fetched post
// We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
redirect(post.slug);
}

如果成功,浏览器将重定向到您想查看的路径,并带有草稿模式 cookie。

第二步:更新页面

接下来,更新页面以检查 draftMode().isEnabled的值。

如果请求包含设置的 cookie,那么数据将在请求时而不是构建时获取。

此外,isEnabled的值将为true

// page that fetches data
import { draftMode } from "next/headers";

async function getData() {
const { isEnabled } = draftMode();

const url = isEnabled
? "https://draft.example.com"
: "https://production.example.com";

const res = await fetch(url);

return res.json();
}

export default async function Page() {
const { title, desc } = await getData();

return (
<main>
<h1>{title}</h1>
<p>{desc}</p>
</main>
);
}
// page that fetches data
import { draftMode } from "next/headers";

async function getData() {
const { isEnabled } = draftMode();

const url = isEnabled
? "https://draft.example.com"
: "https://production.example.com";

const res = await fetch(url);

return res.json();
}

export default async function Page() {
const { title, desc } = await getData();

return (
<main>
<h1>{title}</h1>
<p>{desc}</p>
</main>
);
}

就是这样!如果您从无头 CMS 或手动访问带有secretslug的草稿路由处理器,您现在应该能够查看草稿内容。即使您更新了草稿而未发布,也能查看草稿。

将此设置为无头 CMS 的草稿 URL,或手动访问,您就可以查看草稿内容。

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

更多详情

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

要手动清除草稿模式的 cookie,创建一个调用draftMode().disable()的路由处理器:

import { draftMode } from "next/headers";

export async function GET(request: Request) {
draftMode().disable();
return new Response("Draft mode is disabled");
}
import { draftMode } from "next/headers";

export async function GET(request) {
draftMode().disable();
return new Response("Draft mode is disabled");
}

然后,发送请求到/api/disable-draft来调用路由处理器。如果使用next/link调用该路由,您必须传递prefetch={false}以防止在预取时意外删除 cookie。

每次next build都唯一

每次运行next build时,都会生成一个新的绕过 cookie 值。

这确保无法猜到绕过 cookie 。

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