草稿模式
静态渲染在从无头 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 或手动访问带有secret和slug的草稿路由处理器,您现在应该能够查看草稿内容。即使您更新了草稿而未发布,也能查看草稿。
将此设置为无头 CMS 的草稿 URL,或手动访问,您就可以查看草稿内容。
https://<your-site>/api/draft?secret=<token>&slug=<path>
更多详情
清除草稿模式 cookie
默认情况下,草稿模式会在浏览器关闭时结束。
要手动清除草稿模式的 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 和本地存储访问。