预览

预览功能允许你生成指向前端应用的直接链接。启用后,Admin Panel 的编辑视图中会出现一个"预览"按钮,其 href 指向你提供的 URL。这为编辑人员提供了一种快速导航到前端应用的方式,在那里可以查看该文档数据的呈现效果。否则,他们将不得不自行确定该 URL,这在复杂应用中往往并不直观。

预览功能还可用于实现所谓的"草稿预览"。通过草稿预览,你可以导航到前端应用并进入"草稿模式",此时你的查询会被修改为获取草稿内容而非已发布内容。这对于在发布前查看内容效果非常有用。更多详情

注意: 预览功能与 Live Preview 不同。 Live Preview 会在 Admin Panel 的 iframe 中加载并渲染你的应用,让你可以实时查看更改。而预览功能则是生成指向前端应用的直接链接。

要添加预览功能,可以在任何 Collection ConfigGlobal Config 中向 admin.preview 属性传递一个函数:

import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    preview: ({ slug }) => `http://localhost:3000/${slug}`,
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
    },
  ],
}

选项

preview 函数解析为一个指向你的前端应用的字符串,并带有额外的 URL 参数。这可以是一个绝对 URL 或相对路径,如果需要也可以异步执行。

preview 函数接收以下参数:

Path描述
doc正在编辑的文档数据。这包括尚未保存的更改。
options包含额外属性的对象。

options 对象包含以下属性:

Path描述
locale正在编辑的文档的当前语言环境。
reqPayload 的 Request 对象。
token当前认证用户的 JWT 令牌。

如果你的应用需要一个完全限定的 URL(例如部署到 Vercel 预览部署时),可以使用 req 属性来构建这个 URL:

preview: (doc, { req }) => `${req.protocol}//${req.host}/${doc.slug}` // highlight-line

草稿预览

预览功能可用于实现"草稿预览"。从管理面板点击预览按钮后,你可以在前端应用中进入"草稿模式"。这将允许你调整页面查询以包含 draft: true 参数。当请求中存在此参数时,Payload 会根据文档的 _status 字段返回草稿文档而非已发布的文档。

要进入草稿模式,提供给 preview 函数的 URL 可以指向前端应用中的自定义端点,该端点会设置 cookie 或会话变量来指示草稿模式已启用。这是框架特定的,因此具体实现方式因框架而异,但底层概念是相同的。

Next.js

如果你使用 Next.js,可以按照以下代码进入草稿模式

步骤 1: 格式化预览 URL

首先,格式化你的 admin.preview 函数,使其指向前端应用中你将打开的自定义端点。此 URL 应包含几个关键的查询参数:

import type { CollectionConfig } from 'payload'

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    preview: ({ slug, collection }) => {
      const encodedParams = new URLSearchParams({
        slug,
        collection,
        path: `/${slug}`,
        previewSecret: process.env.PREVIEW_SECRET || '',
      })

      return `/preview?${encodedParams.toString()}` // highlight-line
    },
  },
  fields: [
    {
      name: 'slug',
      type: 'text',
    },
  ],
}

第二步:创建预览路由

接下来,创建一个 API 路由用于验证预览密钥、认证用户并进入草稿模式:

/app/preview/route.ts

import type { CollectionSlug, PayloadRequest } from 'payload'
import { getPayload } from 'payload'

import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

import configPromise from '@payload-config'

export async function GET(
  req: {
    cookies: {
      get: (name: string) => {
        value: string
      }
    }
  } & Request,
): Promise<Response> {
  const payload = await getPayload({ config: configPromise })

  const { searchParams } = new URL(req.url)

  const path = searchParams.get('path')
  const collection = searchParams.get('collection') as CollectionSlug
  const slug = searchParams.get('slug')
  const previewSecret = searchParams.get('previewSecret')

  if (previewSecret !== process.env.PREVIEW_SECRET) {
    return new Response('您无权预览此页面', {
      status: 403,
    })
  }

  if (!path || !collection || !slug) {
    return new Response('搜索参数不足', { status: 404 })
  }

  if (!path.startsWith('/')) {
    return new Response(
      '此端点仅可用于相对路径预览',
      { status: 500 },
    )
  }

  let user

  try {
    user = await payload.auth({
      req: req as unknown as PayloadRequest,
      headers: req.headers,
    })
  } catch (error) {
    payload.logger.error(
      { err: error },
      '实时预览时验证令牌出错',
    )
    return new Response('您无权预览此页面', {
      status: 403,
    })
  }

  const draft = await draftMode()

  if (!user) {
    draft.disable()
    return new Response('您无权预览此页面', {
      status: 403,
    })
  }

  // 可以在此添加额外检查,确认用户是否有权预览此页面

  draft.enable()

  redirect(path)
}

第三步:查询草稿内容

最后,在你的前端应用中,你可以检测草稿模式并调整查询以包含草稿:

/app/[slug]/page.tsx

export default async function Page({ params: paramsPromise }) {
  const { slug = 'home' } = await paramsPromise

  const { isEnabled: isDraftMode } = await draftMode()

  const payload = await getPayload({ config })

  const page = await payload.find({
    collection: 'pages',
    depth: 0,
    draft: isDraftMode, // highlight-line
    limit: 1,
    overrideAccess: isDraftMode,
    where: {
      slug: {
        equals: slug,
      },
    },
  })?.then(({ docs }) => docs?.[0])

  if (page === null) {
    return notFound()
  }

  return (
    <main>
      <h1>{page?.title}</h1>
    </main>
  )
}

注意: 要查看完整可运行的示例,请参考官方 Draft Preview ExampleExamples Directory 中。