使用 Vercel + TiDB Cloud + Cloudflare R2 部署 pyq 项目
项目地址:https://github.com/zhjurz/pyq
本文说明如何将 pyq 部署为一套前后端分离、无需自建服务器的服务。最终使用的基础设施为:
Vercel 前端项目:部署 Next.js 前端;
Vercel 后端项目:部署 Express API Serverless Functions;
TiDB Cloud:提供 MySQL 兼容的托管数据库;
Cloudflare R2:保存图片、音频、视频等媒体文件;
域名
本文域名以
example.com为示例。请把教程里面其中的域名、数据库名、用户名和密钥替换为自己的真实值;不要把真实密码、Access Key 或 Secret 提交到 GitHub。
1. 部署架构与准备
准备三个稳定域名:
用途 | 示例域名 | 说明 |
|---|---|---|
前端站点 |
| 用户实际访问的网站。也可使用 |
后端 API |
| 后端 Vercel 项目;也可以先使用 Vercel 分配的稳定 Production 域名。 |
R2 媒体域名 |
| Cloudflare R2 Bucket 的公开自定义域名。必须托管在 Cloudflare。 |
请求链路如下:
浏览器
│
▼
Vercel 前端(Next.js,example.com)
│ /api/* 由 Next.js rewrite 同源代理
▼
Vercel 后端(Express Serverless,api.example.com)
├──────────────────────► TiDB Cloud(业务数据)
└──────────────────────► Cloudflare R2(媒体上传、读取)开始前请准备:
Vercel、TiDB Cloud、Cloudflare 三个账号;
一个已托管到 Cloudflare 的域名(R2 自定义域名需要);
本地 Node.js 环境,用于首次初始化线上数据库。
建议将前端、后端作为两个独立 Vercel 项目部署。这样后端函数、前端构建和环境变量相互独立,后续排查与升级更清晰。
2. 创建 TiDB Cloud 数据库
2.1 创建集群和数据库
登录 TiDB Cloud;
创建一个 Serverless Cluster;


在 Connect to your app 选项获取连接信息:

Host;
Port;
Username;
Password;
TLS/SSL 连接要求。
使用 Navicat 等数据库客户端连接 TiDB 进行查看和管理;连接时务必按 TiDB Cloud 的提示开启 SSL/TLS。证书在刚才的网页可下载



2.2 本地初始化线上数据库
从仓库下载源码后,进入后端目录:
cd backend复制并创建本地配置文件:
# Windows PowerShell
Copy-Item .env.example .env编辑 backend/.env,至少配置如下内容。此文件仅用于本地执行数据库初始化,不要提交到 Git:
DB_HOST=你的TiDB主机地址
DB_PORT=TiDB控制台显示的端口
DB_USER=TiDB用户名
DB_PASSWORD=TiDB密码
DB_NAME=moment_blog
DB_SSL=true
# 仅在首次初始化、还不存在管理员时使用
ADMIN_EMAIL=admin@example.com
ADMIN_USERNAME=admin
ADMIN_PASSWORD=请设置强密码
安装依赖并初始化:
npm install
npm run db:initdb:init 是项目唯一的官方数据库初始化命令,它会:
验证数据库连接;
创建缺失的数据表;
补齐当前项目支持的历史兼容字段;
创建站点设置默认记录;
创建默认音乐歌单;
在不存在管理员时创建首个管理员。
该命令可以安全重复运行:它不会删除文章、评论、媒体或其他业务数据,也不会重置已有管理员的密码。
db:init不会替你创建 TiDB/MySQL 数据库本身;CREATE DATABASE是前置步骤。若报Unknown database,请先创建DB_NAME对应的数据库。
3. 创建并配置 Cloudflare R2
Vercel Serverless 的本地文件系统不可持久化。项目中的图片、视频、音频和其他上传文件必须使用对象存储保存,因此生产环境必须配置 R2。
3.1 创建 R2 Bucket
在 Cloudflare Dashboard 中进入:
R2 → Create bucket
例如创建名为:
moment-media
后续该名称对应:
R2_BUCKET=moment-media3.2 创建 S3 兼容 API Token
进入:


创建具备以下权限的令牌:
Object Read & Write
保存以下信息:

Cloudflare Account ID
Access Key ID
Secret Access Key
Bucket 名称这些值只配置在后端 Vercel 项目中,绝不能写到前端环境变量或公开仓库。
3.3 绑定 R2 公开访问域名
在 Bucket 设置中为 R2 绑定自定义公开域名,例如:

media.example.com最终的媒体地址为:
https://media.example.com这个值需要同时写入前端和后端:
frontend.NEXT_PUBLIC_MEDIA_ORIGIN
=
backend.R2_PUBLIC_URL
=
https://media.example.com要求:
必须完全一致;
不要带末尾
/;建议使用稳定的自定义域名,而不是临时测试域名;
R2 自定义域名对应的 DNS Zone 必须托管在 Cloudflare。
3.4 配置 R2 Bucket CORS
上传媒体时,浏览器会拿到后端生成的预签名 URL,然后直接 PUT 到 R2。因此 R2 Bucket 必须单独配置 CORS。
进入:

生产环境可以使用如下模板:
[
{
"AllowedOrigins": [
"https://example.com",
"https://media.example.com",
"http://localhost:3000"
],
"AllowedMethods": ["PUT", "GET", "HEAD"],
"AllowedHeaders": ["Content-Type"],
"ExposeHeaders": ["ETag", "cf-ray", "x-amz-request-id"],
"MaxAgeSeconds": 3600
}
]说明:
AllowedOrigins填写的是前端网页的来源,不是后端域名,也不是 R2 媒体域名;若测试时使用前端 Vercel Production 域名,例如
https://your-frontend.vercel.app,也需要将其加入;http://localhost:3000仅用于本地开发,可按需保留;不建议生产环境使用
"AllowedOrigins": ["*"];后端的
CLIENT_URL/CORS_ALLOWED_ORIGINS不能替代 R2 CORS 配置。
如果上传时遇到 CORS 或 403 错误,请依次检查:
R2 CORS 是否包含当前前端的完整协议和域名;
R2 Token 是否有
Object Read & Write权限;R2_ACCOUNT_ID、R2_ACCESS_KEY_ID、R2_SECRET_ACCESS_KEY与 Bucket 是否匹配;浏览器上传的
Content-Type是否在 CORS 允许范围内;前端和后端媒体域名是否完全一致。
4. 部署后端到 Vercel
4.1 创建后端 Vercel 项目
在 Vercel 中选择:


后端项目建议如下设置:
配置项 | 值 |
|---|---|
Root Directory |
|
Framework Preset |
|
Build Command | 使用默认配置即可 |
Output Directory | 留空 |
项目内的 backend/vercel.json 已配置 API 重写、函数内存/执行时间和豆瓣同步 Cron,无需额外创建入口文件。
建议先部署后端,得到稳定的后端访问地址后,再配置前端。

4.2 环境变量配置
填写如下配置
变量 | 建议值 |
|---|---|
|
|
| 线上 MySQL/TiDB 地址,例如 |
|
|
| 数据库用户名 |
| 数据库密码 |
|
|
|
|
| 独立生成的 32+ 字符随机字符串 |
|
|
| 与前端 |
| 独立生成的 32+ 字符随机字符串 |
|
|
|
|
|
|
| Cloudflare Account ID |
| R2 API Token 的 Access Key ID |
| R2 API Token 的 Secret Access Key |
| R2 Bucket 名称,例如 |
|
|
不需要将
ADMIN_EMAIL、ADMIN_USERNAME、ADMIN_PASSWORD配置到后端 Vercel 项目。它们只供本地首次执行db:init创建管理员时使用。
5. 部署前端到 Vercel
5.1 创建前端项目
再次在 Vercel 中创建一个项目,导入同一个 GitHub 仓库。
配置项 | 值 |
|---|---|
Root Directory |
|
Framework Preset |
|
5.2 环境变量配置
在前端 Vercel 项目中配置:
变量 | 建议值 | 说明 |
|---|---|---|
|
| 保持相对路径,浏览器请求先到前端,再由 rewrite 转到后端。 |
|
| 后端 Vercel 域名或绑定的 API 域名。不加 |
|
| 前端最终访问域名。 |
|
| 必须与后端 |
| 生成的一段高强度随机字符串 | 必须和后端的 |
以下两组值必须严格对应:
frontend.REVALIDATE_SECRET
=
backend.REVALIDATE_SECRETfrontend.NEXT_PUBLIC_MEDIA_ORIGIN
=
backend.R2_PUBLIC_URL6. 绑定域名与 Vercel Preview 注意事项
6.1 绑定稳定的 Production 域名
建议绑定:
前端:example.com 或 www.example.com
后端:api.example.com
R2:media.example.com在 Vercel 项目设置中添加前端和后端域名;按 Vercel 给出的提示,在 DNS 中添加对应的 A/CNAME 记录。
绑定完成后,请再次核对:
# 前端
BACKEND_URL=https://api.example.com
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_MEDIA_ORIGIN=https://media.example.com
# 后端
CLIENT_URL=https://example.com
R2_PUBLIC_URL=https://media.example.com修改环境变量后,前端和后端项目都需要重新部署一次。
6.2 不要依赖每次构建产生的 Preview URL
Vercel 分支预览链接通常会随分支或构建变化,例如:
https://project-git-feature-user.vercel.app这类地址不适合作为长期写死的:
BACKEND_URL
CLIENT_URL
NEXT_PUBLIC_SITE_URL
R2 CORS AllowedOrigins建议:
日常生产使用稳定的 Production 域名;
如必须测试 Preview,请使用稳定的预览域名,或将实际 Preview 完整域名同时加入后端 CORS 与 R2 CORS;
不要在生产 R2 CORS 中长期使用
*作为允许来源。
7. 部署后检查清单
7.1 后端健康检查
打开:
https://api.example.com/api/health预期返回:
{
"status": "ok",
"timestamp": "2026-01-01T00:00:00.000Z"
}时间字段会是实际服务器时间,不会与示例完全相同。
恭喜!您已完成安装

