feat: 接入预览模式(Next.js Draft Mode)

This commit is contained in:
2026-08-07 15:34:59 +08:00
parent 1abadd04b8
commit 2457d5ec69
9 changed files with 203 additions and 6 deletions
+54
View File
@@ -0,0 +1,54 @@
import { NextRequest, NextResponse } from "next/server";
import { draftMode, cookies } from "next/headers";
import {
fetchPreviewPage,
resolvePreviewPath,
PREVIEW_CONTENT_TYPE_COOKIE,
PREVIEW_TOKEN_COOKIE,
} from "@/services/preview.service";
import type { WagtailPageSummary } from "@/types/wagtail";
/**
* Wagtail 编辑器点击"预览"后会重定向到此路由(详见 wagtailcms/settings/base.py
* 中 WAGTAIL_HEADLESS_PREVIEW.CLIENT_URLS)。校验 token 有效后开启 Next.js
* Draft Mode 并重定向到对应内容的前端路由,由页面组件通过 getPreviewOrFallback
* 拉取草稿数据渲染。
*/
export async function GET(request: NextRequest) {
const contentType = request.nextUrl.searchParams.get("content_type");
const token = request.nextUrl.searchParams.get("token");
if (!contentType || !token) {
return NextResponse.json(
{ error: "缺少 content_type 或 token 参数" },
{ status: 400 },
);
}
// 先校验 token 是否有效并取回 slug,未开启 Draft Mode,避免暴露无效预览。
const page = await fetchPreviewPage<WagtailPageSummary>(contentType, token);
const path = page ? resolvePreviewPath(contentType, page) : null;
if (!page || !path) {
return NextResponse.json(
{ error: "预览链接无效或已过期" },
{ status: 404 },
);
}
const draft = await draftMode();
draft.enable();
const isProduction = process.env.NODE_ENV === "production";
const cookieStore = await cookies();
const cookieOptions = {
httpOnly: true,
sameSite: "lax" as const,
secure: isProduction,
path: "/",
};
cookieStore.set(PREVIEW_CONTENT_TYPE_COOKIE, contentType, cookieOptions);
cookieStore.set(PREVIEW_TOKEN_COOKIE, token, cookieOptions);
return NextResponse.redirect(new URL(path, request.url));
}