被部署环境折腾过几回之后,结论是“本地能跑不代表线上能跑”:本地构建通过的 Next.js 应用,推上线后 Webhook 签名校验失败、GitHub App 私钥的换行被吞掉、数据库连接串在不同环境对不上。试过自建服务器和容器方案,最后还是选了 Vercel——它对 Next.js 的零配置支持省掉了大量构建脚本,而 Webhook、GitHub App 私钥、托管数据库这些工程细节,恰恰是官方文档不太讲、却决定线上能否跑稳的地方。Vercel 适合 Next.js 应用的零配置部署,但对涉及 Webhook、GitHub App 私钥、数据库的项目,部署时要重点处理环境变量、构建钩子、Edge Runtime 限制、Webhook 公网回调与冷启动。本项目最终采用“Next.js 14 App Router + Vercel + 托管 Postgres + Upstash Redis”的组合,部署用 Git 集成 + Preview 部署,下面把每一处关键配置拆开讲。
一、为什么选 Vercel
选型时对比过三条路:自建服务器、容器平台和 Vercel。自建要自己维护 Nginx、SSL 证书和进程守护,文档翻译工具的核心价值在翻译逻辑,不想把精力耗在运维上;容器平台能拿到完整 Node 环境,但对小团队来说复杂度偏高。Vercel 直接对接 GitHub,push 即部署,很贴合我们“改完代码马上能看到效果”的开发节奏。几个维度的差异如下:
| 维度 | 价值 |
|---|---|
| 零配置 | 自动识别 Next.js,构建无需手写脚本 |
| 预览部署 | 每个 PR 一个独立 URL |
| 边缘网络 | 全球低延迟 |
| Edge Functions | 鉴权逻辑可跑边缘 |
| 与 Git 集成 | push 即部署 |
其中 Preview 部署是团队协作里收益很明显的一项:每个 PR 自动生成独立 URL,评审的人不用自己拉分支跑本地环境,点开链接就能验收翻译效果。这点在给非技术协作者做演示时特别省事,对方不用装任何开发工具。
二、项目结构
代码仓库拆成多个包,Vercel 只关心前端那个应用,其余包作为依赖被引用。目录划分如下:
apps/web # Next.js 前端
apps/worker # Node 任务(可选放 Vercel Cron)
packages/db # Prisma schema 与 client
packages/ai # AI 客户端
Vercel 项目 root 指向 apps/web。
这样划分的好处是前端、任务、数据库层互不依赖各自的构建流程。一开始我图省事把全部代码塞进一个应用里,结果翻译任务挤占 Web 请求的资源,排查问题也要在混合代码里翻找;拆开后每个包职责单一,packages/db 和 packages/ai 被前端和 worker 共享,改数据库 schema 只需要动一处,两边都同步生效。
三、Vercel 项目配置
3.1 框架预设
框架预设保持 Next.js 默认即可,Vercel 会自动识别。真正要改的是 Build Command——仓库是 monorepo,直接执行根目录构建会连 worker 一起打包。我把它指到 web 包,配合 turbo 做增量缓存:
- Framework Preset: Next.js
- Build Command:
cd ../.. && pnpm turbo run build --filter=web - Output:
.next - Install Command:
pnpm install --frozen-lockfile
用 --filter=web 锁定构建范围,install 加 --frozen-lockfile 保证依赖版本和 lockfile 一致,避免本地装了新依赖、线上却按旧锁文件安装导致的版本漂移。
3.2 环境变量
项目涉及 GitHub App 私钥、数据库、Redis、AI 服务密钥,环境变量要一次性配齐。清单如下:
GITHUB_APP_ID=...
GITHUB_APP_PRIVATE_KEY=... # 整段 PEM,注意换行 \n
GITHUB_APP_CLIENT_ID=...
GITHUB_APP_CLIENT_SECRET=...
GITHUB_APP_WEBHOOK_SECRET=...
NEXTAUTH_URL=https://docs-translator.example.com
NEXTAUTH_SECRET=... # openssl rand -base64 32
DATABASE_URL=postgres://...
REDIS_URL=rediss://...
OPENROUTER_API_KEY=...
GITHUB_APP_PRIVATE_KEY 含换行,必须用 \n 转义后粘贴,运行时用 Buffer.from(key, "base64") 或 replace(/\\n/g, "\n") 还原。
这一项踩坑不少。第一次部署时我把 PEM 原文整段粘进 Vercel 控制台,保存时换行被吞掉,GitHub App 签名始终验不过,日志里只报一个笼统的 401。折腾半天才意识到是私钥换行丢失,改成 \n 转义后一次通过。另外注意 NEXTAUTH_SECRET 别用固定字符串,用 openssl rand -base64 32 生成随机值,避免会话密钥可预测。
四、部署模式
4.1 Production
默认 main 分支触发,部署到生产域名。
生产部署绑定主分支,merge 即上线,改动可追溯。项目早期的部署是手动点按钮,发版前后还要检查一遍环境变量,绑定 Git 后这些动作都自动完成。
4.2 Preview
每个 PR 自动部署,URL 类似 docs-translator-git-feature-username.vercel.app,用于演示和评审。
Preview 部署默认使用生产环境变量,这有风险:PR 里的翻译任务如果连的是生产数据库,测试数据会污染线上。我后来给 Preview 单独配了一套环境变量,指到独立的测试库,改动验证完再决定是否合并。
4.3 Edge Middleware
把会话校验放在 middleware.ts,跑 Edge Runtime:
import { auth } from "@/lib/auth";
export default auth((req) => {
if (!req.auth && req.nextUrl.pathname.startsWith("/dashboard")) {
return Response.redirect(new URL("/login", req.url));
}
});
中间件在边缘节点执行,未登录请求在到达后端前就被拦截,Dashboard 类页面的保护不用进 Node 函数。注意 Edge Runtime 不提供完整的 Node API,依赖 fs、crypto 这类模块的逻辑不要塞进中间件,否则线上直接报错。
五、Webhook 配置
Webhook URL 配置为 https://docs-translator.example.com/api/webhook/github。注意:
- 必须使用 https;
- 路径稳定,不在中间件里改写;
- 接收函数禁用 Edge Runtime(需要 Node API 校验签名):
export const runtime = "nodejs";
这三条是从失败里总结出来的。最开始我把 Webhook 路径放在中间件后面做了一次改写,GitHub 收到的签名是针对原始 URL 计算的,改写后验签必失败。后来把路径固定住,接收函数显式声明 runtime = "nodejs",签名校验才稳定下来。GitHub 不接受 http 回调,这一点在配置时就写死,不存侥幸心理。
六、数据库与缓存
- Postgres 推荐 Neon 或 Supabase,Vercel Postgres 也可;
- Redis 推荐 Upstash,与 Vercel 集成度好;
- 两者都通过环境变量接入,业务代码不感知。
数据库选 Neon 而不是自建,图的是 serverless 场景下连接池能自动伸缩,冷启动时不用重建一堆连接。Redis 用 Upstash 的原因类似,REST 接口对 Serverless 友好,不用维护长连接。业务代码只读环境变量,换供应商时改配置即可,代码一行不用动。
七、Cron 任务
Vercel Cron 替代外部调度器:
{
"crons": [
{ "path": "/api/cron/sweep-stale", "schedule": "0 * * * *" }
]
}
用于清理超时任务与刷新缓存。
原本打算用外部定时服务调用接口,但多一个依赖就多一个故障点。Vercel Cron 配置在 vercel.json 里,随仓库一起版本化,删除项目时也不会残留调度任务。免费额度每小时一次,足够本项目清理超时任务和刷新缓存。
八、冷启动
- Serverless 函数冷启动 ~300ms,对 Webhook 影响小;
- 长任务用 Vercel Functions 最大 60s 仍不足,移到外部 worker(Render / Fly.io);
- 关键 token 缓存到 Redis 缩短冷启动耗时。
冷启动是 Serverless 的固有代价,页面请求能接受几百毫秒延迟,但翻译任务经常跑超过一分钟,Vercel 函数超时后任务直接中断。我按耗时分流:Webhook 只负责入队,真正的翻译执行放到外部 worker,Vercel 侧只保留轻量逻辑。GitHub token 这类反复用到的凭证缓存进 Redis,减少每次都要重新申请的开销。
九、监控与日志
- Vercel Analytics:性能、错误率;
- Log Drain:把日志发到 Datadog / Sentry;
- Webhook 健康:Vercel Function logs 中查看 4xx/5xx。
翻译工具出错不像普通网站那样影响可见页面,用户往往在很久之后才发现某个仓库的翻译没生成。所以我把 Webhook 相关的 4xx/5xx 单独盯起来,配合 Log Drain 把结构化日志送进 Sentry,异常能第一时间收到告警。Vercel Analytics 看性能曲线,错误率异常时再顺着日志下钻定位。
十、回滚
- 失败部署可在 Vercel 控制台一键回滚到上一个 production 部署;
- 数据库迁移必须向后兼容,避免回滚后表结构对不上。
代码回滚容易,数据库回滚难。跑 Prisma migrate 时如果迁移不可逆,回滚到旧代码会遇到表结构不匹配。我给迁移定了个规则:只加字段不删字段,先发代码后删旧列,保证任何时刻新旧版本都能共存。
十一、CI/CD 流程
部署链路与 CI 串起来后,发布从手动操作变成了自动流水线:
- PR 触发 preview 部署 + e2e 测试;
- merge 到 main 触发生产部署;
- 部署后跑冒烟测试;
- 自动发版通知到 Slack。
e2e 测试在 preview 环境跑,用独立数据库,不碰生产数据。冒烟测试覆盖登录、Webhook 回调和一次真实翻译,任何一步失败都拦在发版之前。这套流程跑通后,发布频率从每周一次提到每天数次,出错率反而下降,因为每次改动都经过同样的校验路径。
十二、安全
- Vercel 项目开启 “Vercel Authentication” 防止误访问;
- 域名强制 https;
- 环境变量按环境分:Production / Preview / Development。
安全配置不需要多复杂,把几个开关打开就能挡住大部分问题。域名强制 https 是 Vercel 默认行为,证书自动续期。容易被忽略的是 Preview 环境变量:默认继承生产值,一旦有人在 PR 里连了生产数据库,数据风险就大了,务必按环境拆分。
十三、成本优化
- 图片走 Vercel Image Optimization;
- 静态资源长缓存;
- Edge 函数按需启用;
- 函数内存按真实使用调整。
项目初期把函数内存拉到 1GB 图省事,账单数字并不好看。观察一段时间后,实际峰值内存连一半都不到,降回默认档位就够用。图片优化交给 Vercel 处理后,CDN 缓存了转换结果,源站请求量明显减少。成本优化讲究按需分配,不用的能力别开着。
十四、给同类项目的建议
最后整理几条对同类型项目的通用建议:
- 提前设计好 Webhook 路径与签名校验;
- 主密钥永远不要入代码;
- Preview 部署一定要绑独立数据库;
- 把长任务剥离 Serverless。
前两条解决部署后的稳定性问题,后两条解决数据安全和任务超时问题。文档翻译工具这类“Web 前端 + 长任务 + 外部 API”组合的项目,把这四点做扎实,Vercel 部署基本不会踩大的坑。
常见问题(FAQ)
Q1:Vercel 适合长任务吗?
不适合,最长 60s(Pro 900s),超长任务要外迁。
Q2:Webhook 一定要 https 吗?
是,GitHub 不接受 http。
Q3:能不能把私钥放在 Vercel 环境变量?
可以,但生产建议用 KMS。