将 Payload CMS 部署到 Cloudflare Workers 全攻略
在现代 Web 开发中,将强大的无头(Headless)CMS 与高性能的边缘计算平台相结合,可以为开发者和最终用户带来极致的性能和体验。Payload CMS 以其灵活性和出色的开发者体验著称,而 Cloudflare Workers 则提供了全球分布式的 Serverless 计算能力。
本教程将详细引导您完成每一步,将一个功能完备的 Payload CMS 应用部署到 Cloudflare 的全球网络上,并集成 Cloudflare D1 数据库和 R2 存储。
一、引言
1. 什么是 Payload CMS?
Payload CMS 是一个基于 Node.js 和 TypeScript 构建的开源无头 CMS。它不仅仅是一个内容管理系统,更是一个应用程序框架。它的核心优势在于:
- 高度可扩展:通过代码优先(Code-First)的方式定义数据结构,让定制化变得简单直观。
- 优秀的开发者体验:提供类型安全的 API、强大的插件架构和美观易用的管理后台。
- 功能集丰富:内置身份验证、文件上传、版本控制、多语言支持等高级功能。
2. 什么是 Cloudflare Workers?
Cloudflare Workers 是一个 Serverless 计算平台,它允许您在全球数以百计的 Cloudflare 边缘节点上运行 JavaScript 和 WebAssembly 代码。这意味着您的代码离用户更近,从而显著降低延迟。其核心优势包括:
- 全球边缘部署:代码自动部署到全球网络,无需管理服务器或容器。
- 高性能:基于 V8 Isolates 技术,启动速度极快。
- 自动扩缩容:根据流量自动扩展,从容应对流量高峰。
- 成本效益高:按实际使用量付费,并有慷慨的免费额度。
3. 为什么选择将 Payload CMS 部署在 Cloudflare 上?
将 Payload CMS 部署在 Cloudflare Workers 上,您可以获得"鱼和熊掌兼得"的完美组合:
- 极致性能:API 和静态资源都通过 Cloudflare 的边缘网络提供服务,全球用户都能获得飞快的访问速度。
- 无缝生态集成:可以轻松利用 Cloudflare 的整个生态系统,如用 D1 替代传统数据库,用 R2 替代 S3 作为对象存储,从而构建一个完全运行在边缘的全栈应用。
- 简化运维:告别复杂的服务器管理和运维工作,专注于业务逻辑开发。
二、准备工作 (Prerequisites)
在开始之前,请确保您已准备好以下环境和工具:
账户注册:
- 一个 Cloudflare 账户。
- 一个 Payload CMS 账户(如果需要使用其云服务)。
环境和工具安装:
- Node.js (建议使用 v18 或更高版本) 和 npm (或 pnpm/yarn)。
- Cloudflare 的命令行工具 Wrangler。通过以下命令进行全局安装并登录:
npm install -g wrangler
wrangler login
Cloudflare 服务开通:
- 登录您的 Cloudflare 仪表板,确保 Workers, D1 数据库, 和 R2 存储服务都已启用。
三、部署步骤详解
接下来,我们将一步步完成部署。
步骤一:创建并初始化 Payload CMS 项目
首先,使用官方的 CLI 工具创建一个新的 Payload 项目。
npx create-payload-app my-payload-project
在交互式设置中,请确保选择 TypeScript 模板。进入项目目录:
cd my-payload-project
步骤二:配置数据库 - Cloudflare D1
默认情况下,Payload 使用 MongoDB。我们需要将其替换为 Cloudflare D1。
创建 D1 数据库: 使用 Wrangler CLI 创建一个新的 D1 数据库。请记下返回的 database_name 和 database_id。
wrangler d1 create my-payload-db
安装必要的依赖:
npm install @payloadcms/db-d1-sqlite @payloadcms/payload-cloud @payloadcms/richtext-lexical @payloadcms/storage-r2 @opennextjs/cloudflare
配置 Payload 使用 D1:
创建或更新 src/payload.config.ts
文件,使用现代化的配置方式:
// src/payload.config.ts
import { sqliteD1Adapter } from '@payloadcms/db-d1-sqlite'
import { payloadCloudPlugin } from '@payloadcms/payload-cloud'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import path from 'path'
import { buildConfig } from 'payload'
import { fileURLToPath } from 'url'
import { CloudflareContext, getCloudflareContext } from '@opennextjs/cloudflare'
import { GetPlatformProxyOptions } from 'wrangler'
import { r2Storage } from '@payloadcms/storage-r2'
import { Users } from './collections/Users'
import { Media } from './collections/Media'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
// 智能获取 Cloudflare 上下文
const cloudflare = process.argv.find((value) => value.match(/^(generate|migrate):?/))
? await getCloudflareContextFromWrangler()
: await getCloudflareContext({ async: true })
export default buildConfig({
admin: {
user: Users.slug,
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Media],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET || '',
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
db: sqliteD1Adapter({ binding: cloudflare.env.D1 }),
plugins: [
payloadCloudPlugin(),
r2Storage({
bucket: cloudflare.env.R2,
collections: { media: true },
}),
],
})
// 从 Wrangler 获取 Cloudflare 上下文的辅助函数
function getCloudflareContextFromWrangler(): Promise<CloudflareContext> {
return import(`${'__wrangler'.replaceAll('_', '')}`).then(({ getPlatformProxy }) =>
getPlatformProxy({
environment: process.env.CLOUDFLARE_ENV,
experimental: { remoteBindings: process.env.NODE_ENV === 'production' },
} satisfies GetPlatformProxyOptions),
)
}
步骤三:创建集合文件
现在我们需要创建必要的集合文件。在上面的配置中,我们引用了 Users
和 Media
集合。
创建用户集合 (src/collections/Users.ts
):
// src/collections/Users.ts
import type { CollectionConfig } from 'payload'
export const Users: CollectionConfig = {
slug: 'users',
auth: true,
admin: {
useAsTitle: 'email',
},
fields: [
// Email added by default
// Add more fields as needed
],
}
创建媒体集合 (src/collections/Media.ts
):
// src/collections/Media.ts
import type { CollectionConfig } from 'payload'
export const Media: CollectionConfig = {
slug: 'media',
upload: {
staticDir: 'media',
imageSizes: [
{
name: 'thumbnail',
width: 400,
height: 300,
position: 'centre',
},
{
name: 'card',
width: 768,
height: 1024,
position: 'centre',
},
],
adminThumbnail: 'thumbnail',
mimeTypes: ['image/*'],
},
fields: [
{
name: 'alt',
type: 'text',
},
],
}
创建 R2 存储桶:
wrangler r2 bucket create my-payload-uploads
关键优势:
- 在上面的
payload.config.ts
中,R2 存储已经通过r2Storage
插件自动配置 - 文件上传会直接存储到 Cloudflare R2,无需额外的配置
- 通过 Cloudflare 绑定系统,实现了无缝的服务集成
步骤四:配置 OpenNext 适配器
根据最新的实践,我们使用 OpenNext 来部署 Payload CMS 到 Cloudflare,这提供了更好的兼容性和性能。
安装 OpenNext:
npm install @opennextjs/cloudflare
创建 OpenNext 配置:
在项目根目录创建 open-next.config.ts
文件:
// open-next.config.ts
import type { OpenNextConfig } from '@opennextjs/cloudflare'
const config: OpenNextConfig = {
default: {
override: {
wrapper: 'cloudflare-node',
converter: 'edge',
incrementalCache: 'dummy',
tagCache: 'dummy',
queue: 'dummy',
},
},
}
export default config
更新 Next.js 配置:
创建或更新 next.config.ts
文件:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
serverComponentsExternalPackages: ['@payloadcms/db-postgres'],
},
images: {
unoptimized: true,
},
typescript: {
ignoreBuildErrors: true,
},
eslint: {
ignoreDuringBuilds: true,
},
}
export default nextConfig
步骤五:配置 Wrangler
创建 wrangler.jsonc
文件(注意使用 .jsonc
扩展名以支持注释):
{
"name": "my-payload-app",
"compatibility_date": "2024-03-20",
"compatibility_flags": ["nodejs_compat"],
"main": ".open-next/worker.js",
"assets": {
"directory": ".open-next/assets",
"binding": "ASSETS"
},
"d1_databases": [
{
"binding": "DB",
"database_name": "my-payload-db",
"database_id": "<你的 D1 数据库 ID>"
}
],
"r2_buckets": [
{
"binding": "R2_BUCKET",
"bucket_name": "my-payload-uploads"
}
],
"vars": {
"PAYLOAD_SECRET": "a_very_long_and_secure_secret_key",
"R2_PUBLIC_DOMAIN": "https://your-public-r2-domain.com",
"CLOUDFLARE_ACCOUNT_ID": "<你的 Cloudflare Account ID>"
}
}
关键差异:
- 使用
.open-next/worker.js
作为入口点 - 配置静态资源目录为
.open-next/assets
- 使用 JSONC 格式支持注释
步骤六:配置构建脚本
更新 package.json
中的脚本,添加必要的构建和部署命令:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"build:open-next": "open-next build",
"deploy": "pnpm run build:open-next && wrangler deploy",
"wrangler": "wrangler",
"migrate:create": "payload migrate:create",
"migrate": "payload migrate"
}
}
步骤七:本地开发与测试
首先进行身份验证:
pnpm wrangler login
本地开发: 对于本地开发,你可以使用标准的 Next.js 开发服务器:
pnpm dev
测试 Cloudflare 环境: 要在本地测试 Cloudflare 环境,需要先构建 OpenNext 版本:
pnpm run build:open-next
pnpm wrangler dev
步骤八:数据库迁移
在首次部署前,创建并运行数据库迁移:
# 创建迁移文件
pnpm payload migrate:create
# 运行迁移(在部署时自动执行)
pnpm payload migrate
步骤九:部署到 Cloudflare 网络
当本地测试通过后,就可以一键部署到全球网络了:
pnpm run deploy
这个命令会:
- 构建 Next.js 应用
- 使用 OpenNext 转换为 Cloudflare Workers 兼容格式
- 运行数据库迁移
- 部署到 Cloudflare
部署完成后,Wrangler 会提供一个 *.workers.dev
的 URL。访问这个 URL,你应该能看到线上运行的 Payload CMS!
四、总结与后续步骤
恭喜你!你已成功将一个强大的 Payload CMS 应用部署到了 Cloudflare 的全球边缘网络上。
**回顾部署流程:**我们从一个标准的 Payload 项目开始,通过配置 D1 数据库和 R2 存储,使用 OpenNext 适配器将 Next.js 应用转换为 Cloudflare Workers 兼容格式,最终通过 Wrangler 部署到全球边缘网络。
**优势总结:**这个现代化的架构为你带来了:
- 全球性能:通过边缘计算实现超低延迟
- 高可用性:Cloudflare 的全球网络保障服务稳定性
- 成本效益:按使用量付费,免费额度丰富
- 简化运维:无需管理服务器基础设施
- 现代技术栈:基于 Next.js 和 OpenNext 的最新实践
已知限制和注意事项:
GraphQL 支持: 目前 GraphQL 在 Workers 环境中的支持还不完整,建议主要使用 REST API。
Bundle 大小限制: 免费版 Workers 有 1MB 的 bundle 大小限制,付费版为 10MB。对于复杂的 Payload 应用,建议使用付费计划。
数据库连接: D1 数据库通过绑定访问,没有传统的连接字符串,这在某些场景下可能需要特殊处理。
后续操作建议:
- 绑定自定义域名:在 Cloudflare 仪表板中为你的 Worker 绑定专业域名
- 配置 Cloudflare Access:为管理后台添加额外的安全保护层
- 启用日志记录:在 Cloudflare 面板中一键启用 API 日志(会消耗配额)
- 设置 CI/CD:将部署流程集成到你的持续集成管道中
- 探索 Payload 功能:深入研究集合、全局设置和字段类型
- 性能优化:配置 CDN 和缓存策略以进一步提升性能