预览(Preview)
页面摘要: Preview(预览)将内容管理器(Content Manager)连接到前端,让编辑者在发布前查看更改。本文档包含:配置预览 URL 的步骤。
借助 Preview(预览)功能,你可以直接从 Strapi 的管理面板预览你的前端应用。这有助于查看在内容管理器(Content Manager)编辑视图(Edit View)中对内容的更新将如何影响最终结果。
- 套餐:免费功能 Live Preview(实时预览)仅适用于 CMS Growth 和 Enterprise 套餐。
- 角色与权限:在「角色 > 插件 - 用户与权限(Users & Permissions)」中的读取(Read)权限
- 启用:应在
config/admin文件中配置 - 环境:在开发(Development)和生产(Production)环境中均可用

配置
-
必须在你的
.env文件中定义以下环境变量,将示例值替换为适当的值:CLIENT_URL=https://your-frontend-app.com PREVIEW_SECRET=your-secret-keyPREVIEW_SECRET密钥是可选的,但使用 Next.js 草稿模式(draft mode)时需要。 -
你的 Strapi 项目的前端应用应已创建并配置好。
配置组件
Preview(预览)功能的配置存储在 the config/admin file 的 preview 对象中,由 3 个关键组件组成:
启用标志
启用或禁用预览功能:
// …
preview: {
enabled: true,
// …
}
// …
允许的源(Allowed origins)
控制哪些域可以访问预览:
// …
preview: {
enabled: true,
config: {
allowedOrigins: env("CLIENT_URL"), // 通常是你的前端应用 URL
// …
}
}
// …
预览处理器(Preview handler)
管理预览逻辑和 URL 生成,如下面的基本示例所示,其中 uid 是内容类型标识符(例如 api::article.article 或 plugin::my-api.my-content-type):
// …
preview: {
enabled: true,
config: {
// …
async handler(uid, { documentId, locale, status }) {
const document = await strapi.documents(uid).findOne({ documentId });
const pathname = getPreviewPathname(uid, { locale, document });
return `${env('PREVIEW_URL')}${pathname}`
},
}
}
// …
URL 生成逻辑 的示例在以下基本实现指南中给出。
预览草稿条目
前端应用查询草稿或已发布内容的策略取决于具体框架。至少存在 3 种策略:
- 使用查询参数,类似
/your-path?preview=true(例如,Nuxt 就是这样工作的) - 重定向到专用的预览路由,如
/preview?path=your-path(例如,Next 的草稿模式(draft mode) 就是这样工作的) - 或使用不同的域进行预览,如
preview.mysite.com/your-path。
当为你的内容类型启用了 草稿与发布(Draft & Publish) 时,你也可以直接在 Preview handler 中利用 Strapi 的 status 参数来处理逻辑,使用以下通用方法:
async handler(uid, { documentId, locale, status }) {
const document = await strapi.documents(uid).findOne({ documentId });
const pathname = getPreviewPathname(uid, { locale, document });
if (status === 'published') {
// 返回已发布版本
}
// 返回草稿版本
},
使用 Next.js 草稿模式的更详细示例在 基本实现指南 中给出。
基本实现指南
按照以下步骤为你的内容类型添加 Preview(预览)能力。
1. [Strapi] 创建 Preview(预览)配置 {#1-create-config}
创建一个新文件 /config/admin.ts(如果已存在则更新它),结构如下:
export default ({ env }) => ({
// 其他与管理面板相关的配置写在这里
// (见 docs.strapi.io/cms/configurations/admin-panel)
preview: {
enabled: true,
config: {
allowedOrigins: env('CLIENT_URL'),
async handler (uid, { documentId, locale, status }) => {
// 处理器实现见第 3 步
},
},
},
});
2. [Strapi] 添加 URL 生成逻辑 {#2-add-url-generation}
使用 getPreviewPathname 函数添加 URL 生成逻辑。以下示例取自 Launchpad Strapi 演示应用:
// 根据内容类型和文档生成预览路径名的函数
const getPreviewPathname = (uid, { locale, document }): string => {
const { slug } = document;
// 使用各自的 URL 模式处理不同的内容类型
switch (uid) {
// 处理具有预定义路由的页面
case "api::page.page":
switch (slug) {
case "homepage":
return `/${locale}`; // 本地化的首页
case "pricing":
return "/pricing"; // 定价页面
case "contact":
return "/contact"; // 联系页面
case "faq":
return "/faq"; // 常见问题页面
}
// 处理产品页面
case "api::product.product": {
if (!slug) {
return "/products"; // 产品列表页
}
return `/products/${slug}`; // 单个产品页面
}
// 处理博客文章
case "api::article.article": {
if (!slug) {
return "/blog"; // 博客列表页
}
return `/blog/${slug}`; // 单个文章页面
}
default: {
return null;
}
}
};
// … 主导出(见第 3 步)
如果某些内容类型没有意义,则不需要有预览,因此 default 分支会返回 null。例如,带有一些站点元数据的全局单一类型(Global single type)就没有匹配的前端页面。在这些情况下,处理器函数应返回 null,预览 UI 将不会在管理面板中显示。这就是你按内容类型启用或禁用预览的方式。
3. [Strapi] 添加处理器逻辑 {#3-add-handler}
创建完整的配置,在第 1 步创建的基础配置中扩展第 2 步创建的 URL 生成逻辑,添加适当的处理器逻辑:
const getPreviewPathname = (uid, { locale, document }): string => {
// … 如第 2 步所定义
};
// 主配置导出
export default ({ env }) => {
// 获取环境变量
const clientUrl = env("CLIENT_URL"); // 前端应用 URL
const previewSecret = env("PREVIEW_SECRET"); // 用于预览身份验证的密钥
return {
// 其他与管理面板相关的配置写在这里
// (见 docs.strapi.io/cms/configurations/admin-panel)
preview: {
enabled: true, // 启用预览功能
config: {
allowedOrigins: clientUrl, // 将预览访问限制在特定域
async handler(uid, { documentId, locale, status }) {
// 从 Strapi 获取完整的文档
const document = await strapi.documents(uid).findOne({ documentId });
// 根据内容类型和文档生成预览路径名
const pathname = getPreviewPathname(uid, { locale, document });
// 如果未找到路径名,则禁用预览
if (!pathname) {
return null;
}
// 使用 Next.js 草稿模式(draft mode),向其传递密钥和内容类型状态
const urlSearchParams = new URLSearchParams({
url: pathname,
secret: previewSecret,
status,
});
return `${clientUrl}/api/preview?${urlSearchParams}`;
},
},
},
};
};
4. [前端] 设置前端预览路由 {#4-setup-frontend-route}
设置前端预览路由高度依赖于前端应用所使用的框架。
例如,Next.js 草稿模式(draft mode) 和 Nuxt 预览模式 在其各自的文档中提供了如何实现前端部分的额外文档。
如果使用 Next.js,一个基本实现可能类似于以下取自 Launchpad Strapi 演示应用的示例:
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 url = searchParams.get("url");
const status = searchParams.get("status");
// 检查 secret 和 next 参数
// 此 secret 应只有该路由处理器和 CMS 知晓
if (secret !== process.env.PREVIEW_SECRET) {
return new Response("Invalid token", { status: 401 });
}
// 通过设置 cookie 启用草稿模式(Draft Mode)
if (status === "published") {
draftMode().disable();
} else {
draftMode().enable();
}
// 重定向到所获取文章的路径
// 我们不重定向到 searchParams.slug,因为那可能导致开放重定向漏洞
redirect(url || "/");
}
5. [前端] 允许前端被嵌入 {#5-allow-frontend-embed}
在 Strapi 一侧,the allowedOrigins configuration parameter 允许管理面板在 iframe 中加载前端窗口。但允许嵌入是双向的,因此在前端一侧,你也需要允许该窗口被嵌入到 Strapi 的管理面板中。
这要求前端应用拥有自己的响应头指令,即 CSP frame-ancestors 指令。设置此指令取决于你的网站是如何构建的。例如,在 Next.js 中设置它需要一个中间件配置(见 Next.js 文档)。
6. [前端] 调整数据获取以适配草稿内容 {#6-fetch-draft-content}
预览系统设置好后,你需要调整数据获取逻辑,以适当地处理草稿内容。这涉及以下步骤:
- 创建或调整你的数据获取工具,以检查草稿模式是否启用
- 在适当的时候更新你的 API 调用,以包含草稿状态参数
以下示例取自 Launchpad Strapi 演示应用,展示了如何在你的 Next.js 前端应用中实现能够感知草稿的数据获取:
import { draftMode } from "next/headers";
import qs from "qs";
export default async function fetchContentType(
contentType: string,
params: Record = {}
): Promise {
// 检查 Next.js 草稿模式是否启用
const { isEnabled: isDraftMode } = await draftMode();
try {
const queryParams = { ...params };
// 当草稿模式启用时,添加 status=draft 参数
if (isDraftMode) {
queryParams.status = "draft";
}
const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(
`Failed to fetch data from Strapi (url=${url}, status=${response.status})`
);
}
return await response.json();
} catch (error) {
console.error("Error fetching content:", error);
throw error;
}
}
然后,这个工具方法可以在你的页面组件中使用,根据预览状态获取草稿或已发布内容:
// 在你的页面组件中:
const pageData = await fetchContentType('api::page.page', {
// 你的其他查询参数
});
Live Preview(实时预览)实现
(Growth 计划) (Enterprise 计划)
设置好基础的 Preview(预览)功能后,你可以通过实现 Live Preview(实时预览)来增强体验。
窗口消息(Window messages)
Live Preview(实时预览)通过在管理面板和你的前端之间进行通信,创造了更具交互性的体验。它依赖于通过 window 对象上的 the postMessage() API 发送的事件。
你需要在应用中添加一个事件监听器。它应当存在于所有页面上,最好是在包裹整个应用的布局(layout)组件中。监听器需要过滤消息,仅对 Strapi 发起的消息做出反应。
有 2 种消息需要监听:
strapiUpdate:当内容更新已保存到数据库时由 Strapi 发送。这是一个获取内容更新版本并刷新预览的时机。在 Next.js 中,刷新 iframe 内容的推荐方式是使用 therouter.refresh()method。previewScript:由 Strapi 发送,为你提供一个驱动 Live Preview(实时预览)功能的脚本。这个脚本应当被注入到页面的<head>标签中。它负责在预览中高亮可编辑区域,并在双击某个区域进行编辑时向 Strapi 回传消息。
为了接收 previewScript 消息,你需要让 Strapi 知道你的前端已准备好接收它。这通过向父窗口发送 previewReady 消息来完成。
综合起来,一个准备添加到你的全局布局中的组件可能如下所示:
JavaScript
'use client';
export default function LivePreview() {
// …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message) => {
const { origin, data } = message;
if (origin !== process.env.NEXT_PUBLIC_API_URL) {
return;
}
if (data.type === 'strapiUpdate') {
router.refresh();
} else if (data.type === 'strapiScript') {
const script = window.document.createElement('script');
script.textContent = data.payload.script;
window.document.head.appendChild(script);
}
};
// 添加事件监听器
window.addEventListener('message', handleMessage);
// 让 Strapi 知道我们已准备好接收脚本
window.parent?.postMessage({ type: 'previewReady' }, '*');
// 在卸载时移除事件监听器
return () => {
window.removeEventListener('message', handleMessage);
};
}, [router]);
return null;
}
TypeScript
'use client';
export default function LivePreview() {
// …
const router = useRouter();
useEffect(() => {
const handleMessage = async (message: MessageEvent<any>) => {
const { origin, data } = message;
if (origin !== process.env.NEXT_PUBLIC_API_URL) {
return;
}
if (data.type === 'strapiUpdate') {
router.refresh();
} else if (data.type === 'strapiScript') {
const script = window.document.createElement('script');
script.textContent = data.payload.script;
window.document.head.appendChild(script);
}
};
// 添加事件监听器
window.addEventListener('message', handleMessage);
// 让 Strapi 知道我们已准备好接收脚本
window.parent?.postMessage({ type: 'previewReady' }, '*');
// 在卸载时移除事件监听器
return () => {
window.removeEventListener('message', handleMessage);
};
}, [router]);
return null;
}
Next.js 中的缓存:
在 Next.js 中,缓存持久化 可能需要额外的步骤。你可能需要通过从客户端向服务器发起 API 调用来使缓存失效,由服务器处理重新验证(revalidation)逻辑。详情请参阅 Next.js 文档,例如 revalidatePath() 方法。
内容源映射(Content source maps)
Live Preview(实时预览)能够识别前端中对应于 Strapi 字段的部分。这是通过内容源映射(content source maps)实现的,这些元数据被编码为你基于字符串的内容(例如文本字段)中的隐藏字符。它使用 @vercel/stega 库来编码和解码此元数据。
只有在 strapi-encode-source-maps 响应头被设为 true 时,这些元数据才会被添加到你的 Content API 响应中。你可以在数据获取工具中设置此响应头。务必只在检测到你的站点在预览上下文中渲染时才传递该响应头。
:::caution 内容源映射(content source maps)只会添加到 REST API 响应中。如果你的前端通过 GraphQL API 获取内容,则双击编辑不可用。 :::
对于 Next.js 应用,你可以使用 next/headers 中的 draftMode() 方法来检测草稿模式是否启用,并在所有 API 调用中相应地设置该响应头:
import { draftMode } from "next/headers";
import qs from "qs";
export default async function fetchContentType(
contentType: string,
params: Record = {}
): Promise {
// 检查 Next.js 草稿模式是否启用
const { isEnabled: isDraftMode } = await draftMode();
try {
const queryParams = { ...params };
// 当草稿模式启用时,添加 status=draft 参数
if (isDraftMode) {
queryParams.status = "draft";
}
const url = `${baseURL}/${contentType}?${qs.stringify(queryParams)}`;
const response = await fetch(url, {
headers: {
// 在预览模式下启用内容源映射
"strapi-encode-source-maps": isDraftMode ? "true" : "false",
},
});
if (!response.ok) {
throw new Error(
`Failed to fetch data from Strapi (url=${url}, status=${response.status})`
);
}
return await response.json();
} catch (error) {
console.error("Error fetching content:", error);
throw error;
}
}
使用
使用该功能的路径: 内容管理器(Content Manager),你的内容类型的编辑视图
根据你的 CMS 套餐,你使用 Preview(预览)的体验会有所不同:
- 使用免费(Free)套餐时,Preview(预览)仅为全屏模式。
- 使用 (Growth 计划) 和 (Enterprise 计划) 套餐时,你可以获得增强的体验,称为 Live Preview(实时预览)。借助 Live Preview(实时预览),你可以与内容管理器(Content Manager)的编辑视图并排查看预览,并且你还可以通过双击任意内容直接在预览本身中编辑内容。
一旦 Preview(预览)功能设置妥当,内容管理器(Content Manager)的编辑视图 右侧会出现一个 打开预览(Open preview) 按钮。点击它将在你的前端应用中的显示方式预览你的内容,但直接在 Strapi 的管理面板内。

预览打开后,你可以:
- 点击左上角的关闭按钮 返回内容管理器(Content Manager)的编辑视图,
- 使用预览内容上方下拉菜单在桌面(Desktop)和移动(Mobile)预览之间切换,
- 在草稿和已发布版本的预览之间切换(如果为内容类型启用了 草稿与发布(Draft & Publish)),
- 点击右上角的链接图标 以复制预览链接。根据你的当前预览标签页,这将复制草稿或已发布版本的预览链接。
在内容管理器(Content Manager)的编辑视图中,如果你有未保存的更改,打开预览(Open preview) 按钮将被禁用。保存你最新的更改后,你应该能够再次预览内容。
Live Preview(实时预览)
(Growth 计划) (Enterprise 计划)
Live Preview(实时预览)是 Strapi 付费 CMS 套餐中可用的增强型 Preview(预览)体验。
借助 Live Preview(实时预览),除了免费套餐中包含的内容外,你还可以:
- 使用侧边编辑器(Side Editor)并排查看条目的编辑视图(内容管理器(Content Manager)中)和前端预览。你也可以使用 和 按钮在全屏和并排预览之间切换。
- 双击预览窗格中的任意内容以就地编辑。这会打开一个弹出框,将前端内容与 Strapi 中对应的字段同步。

:::caution 实验性功能 此功能目前为实验性。欢迎随时与 Strapi 团队分享 反馈 或 问题。
当前版本的 Live Preview(实时预览)存在以下限制:
- 动态区域(dynamic zones)中的字段暂不受支持。
- 当前端通过 GraphQL API 获取内容时,双击编辑不可用。 :::