Payload CMS 快速上手
Payload CMS 快速上手
如果你受够了 Strapi 的臃肿和 Sanity 的 GROQ 学习曲线,Payload 可能是那个"刚刚好"的无头 CMS。
一、Payload 是什么
Payload 是一个基于 Node.js 的开源无头 CMS,但它与传统 CMS 有几个本质区别:
- 代码优先(Code-first):所有配置都用 TypeScript/JavaScript 写,没有点击拖拽的 GUI 配置,版本控制友好
- 自带管理后台:基于 React + Vite 构建的 Admin UI,无需额外搭建
- 数据库自由:支持 MongoDB(Mongoose)和 PostgreSQL(Drizzle)
- 框架集成:官方提供 Next.js 深度集成,可以直接把 CMS 嵌入到 App Router 中
- 类型安全:从字段定义自动生成 TypeScript 类型,前后端类型一致
简单来说,Payload 是**"一个带有精美管理界面的 Node.js 后端框架"**,而不是一个需要你去适配的黑盒系统。
二、环境准备
Payload 3.0 之后全面拥抱 Next.js,官方推荐的初始化方式也是基于 Next.js:
# 需要 Node.js 18+ 和 pnpm/yarn/npm
npx create-payload-app@latest
初始化向导会询问:
- Project name:项目名称
- Database:选择 MongoDB 或 PostgreSQL(推荐 PostgreSQL,生产更稳)
- Storage:文件存储方式(本地磁盘或 S3/Cloudflare R2 等)
- Template:Blank(空白)或 Website(预置博客模板)
三、项目结构速览
初始化后的目录结构如下:
my-project/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── (payload)/ # Payload 管理后台路由
│ │ └── api/ # API 路由
│ ├── collections/ # 数据集合定义(核心)
│ │ ├── Users.ts
│ │ └── Media.ts
│ ├── payload.config.ts # Payload 全局配置
│ └── payload-types.ts # 自动生成的类型文件
├── media/ # 本地上传的文件
└── docker-compose.yml # 数据库容器(可选)
核心文件解读:
| 文件 | 作用 |
|---|---|
payload.config.ts | 数据库连接、插件、全局设置、集合注册 |
src/collections/*.ts | 定义数据模型(类似 Prisma Schema,但更偏向 CMS 语义) |
src/payload-types.ts | 自动生成,包含所有 Collection 和 Global 的 TypeScript 类型 |
四、核心概念:Collections & Globals
Payload 的数据组织分为两类:
4.1 Collections(集合)
类似数据库表,用于多记录场景:文章、产品、用户、标签等。
// src/collections/Posts.ts
import { CollectionConfig } from 'payload'
export const Posts: CollectionConfig = {
slug: 'posts', // API 端点:/api/posts
admin: {
useAsTitle: 'title', // 后台列表用 title 字段作为标题
},
access: {
read: () => true, // 公开读取
create: ({ req }) => req.user?.role === 'admin', // 仅管理员可创建
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
},
{
name: 'content',
type: 'richText', // 富文本编辑器(Lexical)
},
{
name: 'coverImage',
type: 'upload',
relationTo: 'media', // 关联到 Media 集合
},
{
name: 'publishedAt',
type: 'date',
admin: {
date: {
pickerAppearance: 'dayAndTime',
},
},
},
{
name: 'status',
type: 'select',
options: [
{ label: '草稿', value: 'draft' },
{ label: '已发布', value: 'published' },
],
defaultValue: 'draft',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users', // 关联到 Users 集合
},
{
name: 'tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true, // 多对多
},
],
}
4.2 Globals(全局配置)
用于单记录场景:网站导航、首页 Banner、SEO 设置、页脚信息等。
// src/globals/Settings.ts
import { GlobalConfig } from 'payload'
export const Settings: GlobalConfig = {
slug: 'settings',
access: {
read: () => true,
},
fields: [
{
name: 'siteName',
type: 'text',
},
{
name: 'logo',
type: 'upload',
relationTo: 'media',
},
{
name: 'socialLinks',
type: 'array', // 数组字段
fields: [
{
name: 'platform',
type: 'select',
options: ['twitter', 'github', 'weibo'],
},
{
name: 'url',
type: 'text',
},
],
},
],
}
4.3 注册到配置
// src/payload.config.ts
import { buildConfig } from 'payload'
import { Posts } from './collections/Posts'
import { Tags } from './collections/Tags'
import { Media } from './collections/Media'
import { Settings } from './globals/Settings'
export default buildConfig({
// ...其他配置
collections: [Posts, Tags, Media],
globals: [Settings],
})
修改后重启服务,Payload 会自动:
- 生成对应的数据库表/集合
- 生成
payload-types.ts类型文件 - 在 Admin UI 中渲染出编辑界面
五、实战:5 分钟搭一个博客
步骤 1:创建 Posts 集合
如上文的 Posts.ts,保存到 src/collections/Posts.ts。
步骤 2:创建 Tags 集合
// src/collections/Tags.ts
import { CollectionConfig } from 'payload'
export const Tags: CollectionConfig = {
slug: 'tags',
admin: {
useAsTitle: 'name',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
},
],
}
步骤 3:启动并查看
pnpm dev
访问 http://localhost:3000/admin,使用初始化时创建的账号登录。你会看到左侧导航出现了 Posts、Tags、Media 三个菜单。
点击 Posts → Create New,你会发现:
- 富文本编辑器开箱即用(基于 Lexical,支持 Markdown 快捷输入)
- 关联字段(author、tags)可以直接搜索选择
- 上传字段支持拖拽上传图片
六、前端接入:REST 与 GraphQL
Payload 自动为所有 Collections 和 Globals 生成 REST API 和 GraphQL API。
6.1 REST API 示例
# 获取文章列表(带过滤、排序、分页)
GET /api/posts?where[status][equals]=published&sort=-publishedAt&limit=10
# 获取单篇文章
GET /api/posts/:id
# GraphQL Playground
GET /api/graphql
6.2 Next.js 中接入(Server Component)
// app/blog/page.tsx
import { getPayload } from 'payload'
import config from '@/payload.config'
export default async function BlogPage() {
const payload = await getPayload({ config })
const { docs: posts } = await payload.find({
collection: 'posts',
where: {
status: { equals: 'published' },
},
sort: '-publishedAt',
depth: 1, // 自动填充关联字段(author、tags、coverImage)
})
return (
<main>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
{/* coverImage 已经被 depth:1 填充为完整对象 */}
{post.coverImage && (
<img src={post.coverImage.url} alt={post.title} />
)}
<p>作者:{typeof post.author === 'object' ? post.author.email : ''}</p>
</article>
))}
</main>
)
}
注意:payload.find() 返回的数据类型是自动生成的 Post 类型,全程 TypeScript 提示。
6.3 使用 GraphQL
const QUERY = `
query {
Posts(where: { status: { equals: published } }, limit: 10) {
docs {
id
title
slug
publishedAt
author {
email
}
}
}
}
`
const res = await fetch('/api/graphql', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: QUERY }),
})
七、进阶技巧
7.1 Hooks:数据生命周期
在数据创建/更新/删除前后插入自定义逻辑:
export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
({ data, req, operation }) => {
// 自动生成 slug
if (!data.slug && data.title) {
data.slug = data.title
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
}
return data
},
],
afterChange: [
({ doc, operation }) => {
// 发布后发送 Webhook 通知搜索引擎
if (operation === 'create' && doc.status === 'published') {
// revalidatePath('/blog') 或调用外部 API
}
},
],
},
// ...fields
}
7.2 自定义 Admin 组件
Payload 允许你替换管理后台的几乎任何 UI 组件:
// 在字段中使用自定义组件
{
name: 'seoScore',
type: 'ui', // UI 字段,不存储数据
admin: {
components: {
Field: '@/components/SEOScore#SEOScore',
},
},
}
7.3 本地化(i18n)
// payload.config.ts
export default buildConfig({
localization: {
locales: ['zh', 'en'],
defaultLocale: 'zh',
fallback: true,
},
})
字段级别开启本地化:
{
name: 'title',
type: 'text',
localized: true, // 每种语言独立存储
}
API 调用时通过 ?locale=en 切换语言版本。
八、部署建议
Payload + Next.js 的最佳部署方案:
| 平台 | 说明 |
|---|---|
| Vercel | 适合前端 + Serverless API,但长时间运行的任务(如大文件上传)可能超时 |
| Railway / Render | 容器化部署,适合全栈应用 |
| 自有服务器 + Docker | 生产推荐,配合 docker-compose 编排 Payload + PostgreSQL |
生产 checklist:
- 使用 PostgreSQL 而非 MongoDB(Payload 3.0 对 Postgres 支持更成熟)
- 文件存储迁移到 S3 / Cloudflare R2(使用
@payloadcms/storage-s3) - 配置
PAYLOAD_SECRET环境变量 - 开启数据库连接池(PostgreSQL)
- 设置 Admin UI 的独立域名或 IP 白名单
九、总结
Payload 的定位非常清晰:给开发者用的 CMS。它不提供"零代码"的虚假承诺,而是用代码优先的方式,让你用熟悉的技术栈(TypeScript + React + Node.js)快速构建内容管理后台。
如果你:
- 需要深度定制管理后台
- 希望前后端共享类型定义
- 已经在使用 Next.js 全栈开发
- 受够了传统 CMS 的插件生态和版本升级噩梦
那么 Payload 值得一试。
官方资源:
- 文档:payloadcms.com/docs
- GitHub:payloadcms/payload
- 社区模板:payloadcms.com/templates