Menu

草稿模式

当你的页面从无头 CMS 获取数据时,静态渲染非常有用。但是,当你在无头 CMS 中编写草稿并希望立即在页面上查看草稿时,这就不太理想了。你希望 Next.js 在请求时而不是构建时渲染这些页面,并获取草稿内容而不是已发布内容。你希望 Next.js 只在这种特定情况下切换到动态渲染

Next.js 有一个名为草稿模式的功能,可以解决这个问题。以下是使用它的说明。

第 1 步:创建并访问路由处理程序

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

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

app/api/draft/route.ts
// 启用草稿模式的路由处理程序
import { draftMode } from "next/headers";
 
export async function GET(request: Request) {
  draftMode().enable();
  return new Response("草稿模式已启用");
}
app/api/draft/route.js
// 启用草稿模式的路由处理程序
import { draftMode } from "next/headers";
 
export async function GET(request) {
  draftMode().enable();
  return new Response("草稿模式已启用");
}

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

你可以通过访问 /api/draft 并查看浏览器的开发者工具来手动测试这一点。注意响应头中的 Set-Cookie,其中包含一个名为 __prerender_bypass 的 cookie。

从你的无头 CMS 安全地访问它

在实践中,你会希望从你的无头 CMS 安全地调用这个路由处理程序。具体步骤会因你使用的无头 CMS 而异,但以下是一些你可以采取的常见步骤。

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

首先,你应该使用你选择的令牌生成器创建一个秘密令牌字符串。这个秘密只有你的 Next.js 应用和你的无头 CMS 知道。这个秘密可以防止没有访问你的 CMS 权限的人访问草稿 URL。

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

Terminal
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 指定的路径。
app/api/draft/route.ts
// 带有秘密和 slug 的路由处理程序
import { draftMode } from "next/headers";
import { redirect } from "next/navigation";
 
export async function GET(request: Request) {
  // 解析查询字符串参数
  const { searchParams } = new URL(request.url);
  const secret = searchParams.get("secret");
  const slug = searchParams.get("slug");
 
  // 检查 secret 和 next 参数
  // 这个秘密应该只有这个路由处理程序和 CMS 知道
  if (secret !== "MY_SECRET_TOKEN" || !slug) {
    return new Response("无效的令牌", { status: 401 });
  }
 
  // 调用无头 CMS 检查提供的 `slug` 是否存在
  // getPostBySlug 将实现所需的获取无头 CMS 的逻辑
  const post = await getPostBySlug(slug);
 
  // 如果 slug 不存在,阻止启用草稿模式
  if (!post) {
    return new Response("无效的 slug", { status: 401 });
  }
 
  // 通过设置 cookie 启用草稿模式
  draftMode().enable();
 
  // 重定向到从文章获取的路径
  // 我们不重定向到 searchParams.slug,因为这可能导致开放重定向漏洞
  redirect(post.slug);
}
app/api/draft/route.js
// 带有秘密和 slug 的路由处理程序
import { draftMode } from "next/headers";
import { redirect } from "next/navigation";
 
export async function GET(request) {
  // 解析查询字符串参数
  const { searchParams } = new URL(request.url);
  const secret = searchParams.get("secret");
  const slug = searchParams.get("slug");
 
  // 检查 secret 和 next 参数
  // 这个秘密应该只有这个路由处理程序和 CMS 知道
  if (secret !== "MY_SECRET_TOKEN" || !slug) {
    return new Response("无效的令牌", { status: 401 });
  }
 
  // 调用无头 CMS 检查提供的 `slug` 是否存在
  // getPostBySlug 将实现所需的获取无头 CMS 的逻辑
  const post = await getPostBySlug(slug);
 
  // 如果 slug 不存在,阻止启用草稿模式
  if (!post) {
    return new Response("无效的 slug", { status: 401 });
  }
 
  // 通过设置 cookie 启用草稿模式
  draftMode().enable();
 
  // 重定向到从文章获取的路径
  // 我们不重定向到 searchParams.slug,因为这可能导致开放重定向漏洞
  redirect(post.slug);
}

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

第 2 步:更新页面

下一步是更新你的页面以检查 draftMode().isEnabled 的值。

如果你请求一个设置了 cookie 的页面,那么数据将在请求时获取 (而不是在构建时)。

此外,isEnabled 的值将为 true

app/page.tsx
// 获取数据的页面
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>
  );
}
app/page.js
// 获取数据的页面
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 或手动访问,你应该能够看到草稿。

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

更多细节

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

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

app/api/disable-draft/route.ts
import { draftMode } from "next/headers";
 
export async function GET(request: Request) {
  draftMode().disable();
  return new Response("草稿模式已禁用");
}
app/api/disable-draft/route.js
import { draftMode } from "next/headers";
 
export async function GET(request) {
  draftMode().disable();
  return new Response("草稿模式已禁用");
}

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

每次 next build 唯一

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

这确保了绕过 cookie 不能被猜到。

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