Next.js 与 Payload 同仓 Serverless 部署:@payloadcms/next-payload 集成实战
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
本篇指南围绕 Next.js 仓库中的 cms-payload 示例 展开,讲解如何使用@payloadcms/next-payload将 Payload CMS 以 Serverless 方式与 Next.js 应用部署在同一个仓库中:涵盖本地开发启动流程、所需环境变量、withPayload配置改造、Admin 界面挂载位置,以及内容变更触发页面按需再水合的完整链路。读完之后,你可以独立搭建一套「Next.js 前端 + Payload 内容管理」的同仓部署方案,并理解其背后的实现机制。
方案定位:为什么把 Payload 放进 Next.js
Payload 本身是一个基于 Node.js 的开源 Headless CMS,而 cms-payload 示例 演示的正是官方@payloadcms/next-payload包的能力:让 Payload 以 API 路由和静态资源的形式运行在 Next.js(Pages Router API + App Router Admin)中,从而可以用 Vercel 等边缘/Serverless 平台整体部署,无需单独维护一个 Express 服务。
从 package.json 可以看到该示例的关键依赖组合:
payload@1.9.2:Payload 核心;@payloadcms/next-payload@0.0.27:Next.js 集成层,提供withPayload配置函数和 API 处理器;@payloadcms/plugin-cloud-storage+@aws-sdk/client-s3:将媒体文件存入 S3 而非本地磁盘(Serverless 环境的关键前提);@vercel/edge:为运行在 Vercel Edge 环境提供必要的 Node API 垫片。
快速开始:克隆、安装与本地开发
使用 create-next-app 引导示例
按照 README 的说明,用create-next-app即可拉取本示例:
npx create-next-app --example cms-payload cms-payload-appyarn create next-app --example cms-payload cms-payload-apppnpm create next-app --example cms-payload cms-payload-app本地开发的软件依赖
README 列出的本地开发前置条件:
- MongoDB(Payload 的数据库);
- Node + NPM / Yarn;
- 一个用于存储媒体文件的 S3 Bucket(可选)。
启动步骤
- 克隆仓库(或使用
create-next-app生成的项目); - 运行
yarn或npm install; - 运行
cp .env.example .env,并按.env.example填写全部环境变量; - 运行
yarn dev(即next dev)启动开发服务器; - 访问
http://localhost:3000/admin即可进入 Payload 管理后台。
部署到云端(如 Vercel)时,核心要求只有两点:一个 Mongo Atlas 数据库连接串,以及一个 S3 Bucket(可选)。把.env.example中的变量替换为真实值即可上线。
环境变量全解
.env.example 是理解整个集成的心跳,逐条说明如下:
| 变量 | 用途 |
|---|---|
MONGODB_URI | Payload 使用的 MongoDB 连接串,如mongodb://localhost/payload-vercel-functions;Serverless 部署时填 Mongo Atlas 连接串 |
PAYLOAD_SECRET | Payload 的会话签名密钥 |
PAYLOAD_CONFIG_PATH | Payload 配置文件路径,示例中为dist/payload.config.js |
NEXT_PUBLIC_APP_URL | Next.js 站点的公开地址,默认http://localhost:3000 |
PAYLOAD_PUBLIC_CMS_URL | CMS 的公开地址,用于内容变更后回调解密再生成接口 |
S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY | S3 写权限凭证,供媒体上传使用 |
S3_REGION | S3 区域 |
NEXT_PUBLIC_S3_HOSTNAME/NEXT_PUBLIC_S3_BUCKET | S3 主机名与 Bucket 名,同时用于next/image的远程图片域名白名单 |
PAYLOAD_PRIVATE_REGENERATION_SECRET | Payload 侧调用再生成接口时携带的密钥 |
NEXT_PRIVATE_REGENERATION_SECRET | Next.js 侧再生成接口校验的密钥 |
注意最后两个变量的分工:前者是 Payload 发出的请求凭证,后者是 Next.js 接口端用来验签的,二者必须一致才能触发按需再生成(下文详述)。
withPayload:Next.js 配置的改造点
next.config.js展示了集成层的两个关键动作:
const { withPayload } = require("@payloadcms/next-payload"); const nextConfig = withPayload( { reactStrictMode: true, // 将 /admin 下所有路径回退到 Admin 单页应用入口 rewrites: [{ source: "/admin/(.*)", destination: "/admin/index.html" }], images: { remotePatterns: [ { protocol: "https", hostname: "nextjs-vercel.payloadcms.com", port: "", pathname: "/my-account/**", }, { protocol: "https", hostname: process.env.NEXT_PUBLIC_S3_HOSTNAME, port: "", pathname: `/${process.env.NEXT_PUBLIC_S3_BUCKET}/**`, }, ], }, }, { // 指向 Payload 配置文件 configPath: path.resolve(__dirname, "./payload/payload.config.ts"), }, ); module.exports = nextConfig;三个值得关注的细节:
withPayload包装:它会在构建时为 Payload Admin 生成静态资源,并注入相关 Webpack 规则;第二个参数configPath告诉集成层 payload.config.ts 的位置。/admin路由重写:Payload Admin 是一个单页应用,Next.js 把/admin/*全部指向/admin/index.html,由前端路由接管深层路径。remotePatterns白名单:媒体图片来自 S3,必须把NEXT_PUBLIC_S3_HOSTNAME+ Bucket 前缀加入next/image的远程域名白名单,否则<Image>组件会拒绝加载 CMS 中的图片。
Payload 配置:Collection、S3 媒体存储与类型生成
payload/payload.config.ts是本示例的 CMS 配置核心:
import { buildConfig } from "payload/config"; import { cloudStorage } from "@payloadcms/plugin-cloud-storage"; import { s3Adapter } from "@payloadcms/plugin-cloud-storage/s3"; const adapter = s3Adapter({ config: { endpoint: `https://${process.env.NEXT_PUBLIC_S3_HOSTNAME}`, region: process.env.S3_REGION, forcePathStyle: true, credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID as string, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY as string, }, }, bucket: process.env.NEXT_PUBLIC_S3_BUCKET as string, }); export default buildConfig({ collections: [Pages, Users, Media], globals: [MainMenu], typescript: { outputFile: path.resolve(__dirname, "../payload-types.ts"), }, graphQL: { schemaOutputFile: path.resolve(__dirname, "generated-schema.graphql"), }, plugins: [ cloudStorage({ collections: { media: { adapter, disablePayloadAccessControl: true, }, }, }), ], });要点解析:
- S3 适配器是 Serverless 化的关键:Serverless 平台没有持久本地磁盘,媒体文件必须落在对象存储上。这里通过
@payloadcms/plugin-cloud-storage的s3Adapter接管mediacollection 的文件读写,凭证与 Bucket 全部来自环境变量。 typescript.outputFile:Payload 会在构建/生成时把每个 Collection 的字段类型输出到 payload-types.ts,前端组件因此获得完整的类型推导。package.json中的generate:types脚本(cross-env PAYLOAD_CONFIG_PATH=payload/payload.config.ts payload generate:types)可手动触发该过程。graphQL.schemaOutputFile:同时导出一份 GraphQL Schema,配合后文的 GraphQL API 路由使用。
API 路由:Pages Router 承载 Payload 请求
Payload 的 REST API 在示例中由 Pages Router 的 API 路由承载。以pages/api/[collection]/index.ts为例:
import handler from "@payloadcms/next-payload/dist/handlers/[collection]"; export default handler; export const config = { api: { bodyParser: false, externalResolver: true, }, };整个pages/api/[collection]/目录是一套极薄的转发层:index.ts(列表/查询)、[id].ts(单文档读写)、login.ts、logout.ts、me.ts、refresh.ts、first-register.ts、forgot-password.ts、init.ts、access/[id].ts等路由各自一行,把请求委托给@payloadcms/next-payload打包好的对应 handler。两个config参数值得注意:
bodyParser: false:由 Payload 自行解析请求体(因为它需要处理文件上传等复杂 payload);externalResolver: true:允许这些路由以独立解析器方式挂载,兼容 Serverless 冷启动场景。
此外还有全局与 GraphQL 相关的路由:pages/api/globals/[global]/(全局设置读写)、pages/api/graphql.ts 与pages/api/graphql-playground.ts(GraphQL 端点及调试页)、pages/api/access.ts(权限校验)。
Admin 界面:App Router 中的 Root 组件
Admin 面板位于 App Router 的路由组(payload)中。app/(payload)/admin/page.tsx/admin/page.tsx) 只有十几行:
"use client"; import React from "react"; import Root from "payload/dist/admin/Root"; const PayloadAdmin = () => { const [mounted, setMounted] = React.useState(false); React.useEffect(() => { setMounted(true); }, []); if (!mounted) return null; return <Root />; }; export default PayloadAdmin;这里用客户端状态确保Root只在浏览器端挂载,避免 SSR 时访问window报错。app/(payload)/admin/[...slug]/page.tsx则把/admin下所有深层路径都渲染为同一组件:
// Need to render the same component for anything within /admin export { default } from "../page";前端路由的深度路径由 Payload Admin 自己处理,Next.js 侧只需保证「同一个组件兜底一切子路径」。
内容驱动的前端:RSC 读取 + 按需再生成
服务端组件直接查询 Payload
app/(site)/[slug]/page.tsx展示了内容如何流入 Next.js:
import { getPayloadClient } from "../../../payload/payloadClient"; const Page = async ({ params: { slug } }) => { const payload = await getPayloadClient(); const pages = await payload.find({ collection: "pages", where: { slug: { equals: slug || "home" } }, }); const page = pages.docs[0]; if (!page) return notFound(); return ( <> <AdminBar adminBarProps={{ collection: "pages", id: page.id }} /> <Hero {...page.hero} /> <Blocks blocks={page.layout} /> </> ); }; export async function generateStaticParams() { const payload = await getPayloadClient(); const pages = await payload.find({ collection: "pages", limit: 0 }); return pages.docs.map(({ slug }) => ({ slug })); }- 页面数据通过
getPayloadClient()直连数据库查询(生产部署中通常经由 Payload API),generateStaticParams在构建时枚举全部页面 slug; publishedOnly访问控制(见 payload/access/publishedOnly.ts)保证只有已发布的文档可被前端读取。
Pages collection 定义了一个「标题 + Hero + Blocks 布局 + Slug」的结构,layout字段由CallToAction、Content、MediaBlock三种块组成,与components/Blocks/下的 React 组件一一对应——这就是典型的「结构化内容 → 服务端组件」映射。
从内容变更到页面再水合的完整链路
这是整个示例最有实战价值的部分,链路由三端拼合:
Payload 侧的 afterChange Hook:payload/utilities/regenerateStaticPage.ts挂在 Pages collection 的
afterChange钩子上(见 Pages.ts 中hooks: { afterChange: [regenerateStaticPage] })。文档变更后,它向 Next.js 站点发起请求:const res = await fetch( `${process.env.PAYLOAD_PUBLIC_CMS_URL}/api/regenerate?secret=${process.env.PAYLOAD_PRIVATE_REGENERATION_SECRET}&path=${path}`, );Next.js 侧的再生成接口:pages/api/regenerate.ts校验
secret与NEXT_PRIVATE_REGENERATION_SECRET是否一致,解析出path后调用res.revalidate(path)触发该路径的按需再水合;密钥不匹配返回 401,缺少 path 返回 400。效果:编辑者在
http://localhost:3000/admin修改并保存页面后,对应静态页面会被立即重新生成,无需整站重建。
这条「Hook → 带密钥的内部 API 调用 → revalidate」的链路,是内容管理与静态生成保持同步的通用模式,也解释了.env.example中两个*_REGENERATION_SECRET变量必须配对的原因。
构建与运行命令
package.json 中的脚本一览:
| 脚本 | 说明 |
|---|---|
yarn dev | 启动next dev开发服务器 |
yarn build/yarn build:next | 构建生产版本(withPayload会在构建中一并产出 Admin 资源) |
yarn start | 启动生产服务器 |
yarn install:payload | 执行next-payload install,初始化 Payload 集成所需的构建钩子 |
yarn generate:types | 生成payload-types.ts类型定义 |
yarn generate:graphQLSchema | 导出 GraphQL Schema 文件 |
小结
这个示例给出的是一套边界清晰的最小可行方案:用withPayload改造 Next.js 配置,Pages Router 转发 Payload REST API,App Router 挂载 Admin 单页应用,S3 承载媒体存储,afterChangeHook +revalidate打通内容同步。所有关键文件集中在 examples/cms-payload 目录下,按「next.config.js→pages/api/→app/(payload)/→payload/」的顺序阅读源码,即可完整还原其实现脉络。若你的项目需要更丰富的第三方 CMS 集成参考,同仓库还有 cms-prismic、cms-contentful、cms-wordpress 等示例可对照阅读。
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考