CI/CD 流水线
代码写完了不等于交付了——从代码提交到用户能用上新功能,中间还有构建、测试、审查、部署等一系列步骤。CI/CD(持续集成/持续部署)将这些步骤自动化,确保每次变更都经过严格检验后才上线。本章系统讲解 Next.js 项目的 CI/CD 流水线设计,从 GitHub Actions 配置到 Preview Deployments 到数据库迁移自动化。
1. CI/CD 基础概念
1.1 CI vs CD
| 概念 | 全称 | 含义 | 触发时机 |
|---|---|---|---|
| CI | Continuous Integration | 代码提交后自动构建和测试 | 每次 push / PR |
| CD(Delivery) | Continuous Delivery | 自动化到"准备部署"状态,手动触发上线 | PR 合并后 |
| CD(Deployment) | Continuous Deployment | 自动化到"直接上线",无需人工干预 | PR 合并后 |
大部分 Next.js 团队使用 CI + Continuous Deployment 模式——PR 中跑测试和检查,合并到 main 后自动部署。
1.2 Next.js 项目的 CI/CD 流程
开发者 push 代码
↓
创建 / 更新 PR
↓
CI 流水线启动:
├── 1. 代码检查(ESLint + TypeScript)
├── 2. 单元测试(Vitest)
├── 3. 构建检查(next build)
├── 4. E2E 测试(Playwright)
└── 5. Preview Deployment(Vercel / 自建)
↓
所有检查通过 → PR 可合并
↓
合并到 main
↓
CD 流水线启动:
├── 1. 构建生产版本
├── 2. 数据库迁移(如有)
├── 3. 部署到生产环境
└── 4. 部署后验证(Smoke Test)
2. GitHub Actions 配置
2.1 基础 CI 流水线
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint-and-type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm type-check
unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --coverage
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build2.2 关键配置解析
| 配置项 | 作用 | 重要性 |
|---|---|---|
concurrency + cancel-in-progress | 同一 PR 的新 push 取消旧的运行 | 节省 CI 时间 |
pnpm/action-setup | 安装 pnpm(比 npm 快) | 加速安装 |
actions/setup-node + cache: 'pnpm' | 缓存 node_modules | 加速安装 |
--frozen-lockfile | 确保 lockfile 和 package.json 一致 | 可重复构建 |
| 并行 jobs | lint、test、build 同时运行 | 减少总时间 |
2.3 E2E 测试流水线
E2E 测试比单元测试复杂——需要先构建应用、启动服务器、然后运行 Playwright:
e2e-test:
runs-on: ubuntu-latest
needs: build # 依赖 build job 成功
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm build
- run: pnpm test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/关键点:
needs: build确保构建成功后才跑 E2Eplaywright install --with-deps安装浏览器和系统依赖upload-artifact在失败时上传测试报告,方便调试- 只安装
chromium(而非所有浏览器),加速安装
2.4 CI 时间优化
| 优化策略 | 效果 | 复杂度 |
|---|---|---|
| 依赖缓存 | 跳过 npm install | 低(setup-node 内置) |
| 构建缓存 | 复用上次的 .next/cache | 低 |
| 并行 jobs | lint、test、build 同时跑 | 低 |
| 取消过时运行 | 新 push 取消旧 CI | 低 |
| 只测试变更 | 根据文件变更决定跑哪些测试 | 中 |
| Turborepo Remote Cache | Monorepo 构建缓存共享 | 中 |
| 自托管 Runner | 更强的机器、持久缓存 | 高 |
Next.js 构建缓存配置:
- uses: actions/cache@v4
with:
path: .next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-这个缓存可以将增量构建时间从 2-3 分钟减少到 30 秒以内。
3. Preview Deployments
3.1 什么是 Preview Deployments
Preview Deployments 是现代前端工作流的核心——每个 PR 自动部署一个独立的预览环境,让团队成员在合并前就能看到和测试变更。
开发者创建 PR
↓
CI 检查 + Preview 部署
↓
PR 页面出现预览链接:
🔗 https://my-app-pr-42.vercel.app
↓
产品经理/设计师打开链接 Review
↓
反馈 → 修改 → 新的 Preview 自动更新
↓
通过 → 合并 → Preview 自动清理
3.2 Preview 的价值
| 价值 | 说明 |
|---|---|
| 早期发现问题 | 不用等到合并后才发现 UI 异常 |
| 跨职能协作 | 设计师、产品经理可以直接看效果 |
| 减少"在我机器上没问题" | 预览环境和生产环境一致 |
| E2E 测试目标 | Playwright 可以直接测试预览 URL |
| 加速 Review | Reviewer 不需要本地拉代码构建 |
3.3 各平台 Preview 支持
| 平台 | Preview Deployments | 配置复杂度 | PR 评论集成 |
|---|---|---|---|
| Vercel | ✅ 开箱即用 | 零配置 | ✅ 自动评论 |
| Netlify | ✅ 开箱即用 | 零配置 | ✅ 自动评论 |
| AWS Amplify | ✅ 内置 | 低 | ✅ 自动评论 |
| Cloudflare Pages | ✅ 内置 | 低 | ⚠️ 需配置 |
| 自建(Docker) | ⚠️ 需要自建 | 高 | 需要自己写 |
3.4 自建 Preview 系统
如果不用 Vercel 等平台,可以用 GitHub Actions + Docker 自建 Preview:
PR 创建/更新
↓
GitHub Actions 触发
↓
构建 Docker 镜像
↓
部署到临时环境(命名:preview-pr-{number})
↓
GitHub API 在 PR 中评论预览链接
↓
PR 关闭/合并 → 清理临时环境
所需基础设施:
- Docker Registry(存储 Preview 镜像)
- 容器编排(Kubernetes / Docker Compose)
- 反向代理(Nginx / Traefik,动态路由)
- DNS 通配符(
*.preview.yourdomain.com)
4. 数据库迁移自动化
4.1 为什么数据库迁移是 CI/CD 的难点
数据库迁移不像代码部署可以简单回滚——删了一列就回不来了。CI/CD 中的数据库迁移需要特别谨慎:
| 挑战 | 说明 |
|---|---|
| 不可逆操作 | DROP COLUMN 无法回滚 |
| 锁表风险 | 大表添加索引可能锁定数据库 |
| 零停机 | 迁移不能影响正在运行的应用 |
| Preview 环境 | 每个 PR 可能有不同的 Schema |
| 多实例 | 多个服务器实例同时运行迁移 |
4.2 迁移策略
策略一:部署前迁移(推荐)
CI 构建成功
↓
运行数据库迁移
↓
迁移成功 → 部署新代码
迁移失败 → 中止部署
策略二:应用启动时迁移
部署新代码
↓
应用启动时检查并运行迁移
↓
迁移完成 → 开始接收请求
策略三:独立迁移 Job
迁移 Job(独立运行)
↓
迁移完成 → 通知部署流水线
↓
部署新代码
4.3 安全迁移原则
| 原则 | 说明 | 示例 |
|---|---|---|
| 向后兼容 | 新旧代码都能与当前 Schema 工作 | 先加列,再改代码,最后删旧列 |
| 分步执行 | 大迁移拆成多个小迁移 | 重命名列 = 加新列 + 复制数据 + 删旧列 |
| 避免锁表 | 大表操作用 CONCURRENTLY | CREATE INDEX CONCURRENTLY |
| 有回滚方案 | 每个 up 迁移都有对应的 down | Drizzle/Prisma 自动生成 |
| 先在 staging 跑 | 生产前先在 staging 验证 | CI 中先跑 staging 迁移 |
4.4 CI 中的迁移流程
migrate:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: [lint-and-type-check, unit-test, build]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm db:migrate
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}5. 自动化测试策略
5.1 测试金字塔在 CI 中的应用
E2E Tests ← 少量关键路径(慢,但最接近真实)
/ \
Integration Tests ← API + 数据库(中等速度)
/ \
Unit Tests ← 大量纯逻辑(快速)
/ \
Type Check + Lint ← 最快(秒级完成)
| 层级 | CI 中运行时机 | 运行时间 | 失败率 |
|---|---|---|---|
| Type Check + Lint | 每次 push | < 30s | 低 |
| Unit Tests | 每次 push | < 1min | 低 |
| Integration Tests | PR + merge to main | 1-3min | 中 |
| E2E Tests | PR(关键路径) | 3-10min | 较高(Flaky) |
5.2 测试并行化
GitHub Actions 支持矩阵策略来并行运行测试:
e2e:
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: pnpm test:e2e --shard=${{ matrix.shard }}/44 个 shard 并行运行 E2E 测试,总时间减少到 1/4。
5.3 Flaky Test 处理
| 策略 | 说明 |
|---|---|
| 自动重试 | Playwright 的 retries: 2 配置 |
| 隔离标记 | @flaky 标签标记不稳定测试 |
| 定期清理 | 每周 Review flaky tests,修复或删除 |
| 统计追踪 | 记录每个测试的通过率趋势 |
| 非阻塞 | Flaky tests 不阻塞 PR 合并 |
6. 部署策略
6.1 渐进式部署
| 策略 | 描述 | 适用场景 |
|---|---|---|
| 直接部署 | 新版本直接替换旧版本 | 小项目、个人项目 |
| 蓝绿部署 | 新旧版本并行,切换流量 | 需要快速回滚 |
| Canary 部署 | 先给 5-10% 用户使用新版本 | 大型应用、降低风险 |
| Feature Flags | 代码已部署但功能未开放 | 长期开发的功能 |
6.2 部署后验证(Smoke Test)
部署完成后自动运行基本验证:
smoke-test:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Check health endpoint
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.PROD_URL }}/api/health)
if [ "$STATUS" != "200" ]; then
echo "Health check failed with status $STATUS"
exit 1
fi
- name: Check homepage
run: |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" ${{ env.PROD_URL }})
if [ "$STATUS" != "200" ]; then
echo "Homepage check failed"
exit 1
fi6.3 自动回滚
部署新版本
↓
Smoke Test
├── 通过 → 部署成功 ✅
└── 失败 → 自动回滚到上一个版本 ⚠️
↓
发送告警通知(Slack/Email)
在 Vercel 上回滚非常简单——每个部署都是不可变的,回滚就是将流量切换到上一个部署。
7. Monorepo CI/CD
7.1 Monorepo 的 CI 挑战
| 挑战 | 说明 |
|---|---|
| 全量构建慢 | 改一行代码触发所有项目构建 |
| 依赖关系 | 包 A 改了,依赖它的包 B 也要测试 |
| 部署粒度 | 只部署有变更的应用 |
7.2 Turborepo 集成
Turborepo 是 Vercel 出品的 Monorepo 构建工具,核心能力:
| 能力 | 说明 |
|---|---|
| 任务缓存 | 输入不变则跳过构建 |
| 远程缓存 | CI 间共享构建缓存 |
| 依赖图 | 自动分析包依赖关系 |
| 并行执行 | 无依赖的任务并行运行 |
| 变更检测 | 只构建有变更的包 |
7.3 变更检测策略
detect-changes:
runs-on: ubuntu-latest
outputs:
web-changed: ${{ steps.filter.outputs.web }}
api-changed: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
web:
- 'apps/web/**'
- 'packages/ui/**'
api:
- 'apps/api/**'
- 'packages/db/**'
deploy-web:
needs: detect-changes
if: needs.detect-changes.outputs.web-changed == 'true'
# ... 只在 web 相关文件变更时部署8. 安全与合规
8.1 CI/CD 安全清单
| 检查项 | 说明 | 重要性 |
|---|---|---|
| ✅ Secrets 加密存储 | GitHub Secrets / Vault | 🔴 必须 |
| ✅ 最小权限原则 | CI Token 只给必要权限 | 🔴 必须 |
| ✅ 依赖审计 | pnpm audit / Dependabot | 🟡 推荐 |
| ✅ 环境隔离 | Preview 用独立数据库 | 🟡 推荐 |
| ✅ 审计日志 | 记录所有部署操作 | 🟡 推荐 |
| ✅ 分支保护 | main 分支需要 Review + CI 通过 | 🔴 必须 |
| ✅ 签名验证 | 部署包签名验证 | 🟢 可选 |
8.2 Dependabot / Renovate
自动化依赖更新:
| 工具 | 特点 | 推荐场景 |
|---|---|---|
| Dependabot | GitHub 内置、简单 | 小项目 |
| Renovate | 更灵活、分组更新、自动合并 | 大项目 / Monorepo |
Renovate 的自动合并策略:
补丁版本更新(1.2.3 → 1.2.4)→ CI 通过后自动合并
次版本更新(1.2.0 → 1.3.0)→ CI 通过后自动合并
主版本更新(1.0.0 → 2.0.0)→ 需要人工 Review
本章小结
- CI 流水线:Lint + Type Check + Unit Test + Build + E2E Test,并行执行最大化效率
- GitHub Actions 优化:依赖缓存 + 构建缓存 + 取消过时运行 + 并行 jobs
- Preview Deployments:现代前端工作流的核心,Vercel 零配置支持,自建需要较多基础设施
- 数据库迁移:部署前迁移最安全,遵循向后兼容原则,大迁移分步执行
- 测试策略:测试金字塔 + 并行化 + Flaky Test 管理
- 部署策略:小项目直接部署,大项目 Canary + Feature Flags
- Monorepo:Turborepo 缓存 + 变更检测,只构建和部署有变更的包
- 安全:Secrets 加密、最小权限、分支保护、自动化依赖更新