CI/CD 流水线

代码写完了不等于交付了——从代码提交到用户能用上新功能,中间还有构建、测试、审查、部署等一系列步骤。CI/CD(持续集成/持续部署)将这些步骤自动化,确保每次变更都经过严格检验后才上线。本章系统讲解 Next.js 项目的 CI/CD 流水线设计,从 GitHub Actions 配置到 Preview Deployments 到数据库迁移自动化。

1. CI/CD 基础概念

1.1 CI vs CD

概念全称含义触发时机
CIContinuous 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 build

2.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 一致可重复构建
并行 jobslint、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 确保构建成功后才跑 E2E
  • playwright install --with-deps 安装浏览器和系统依赖
  • upload-artifact 在失败时上传测试报告,方便调试
  • 只安装 chromium(而非所有浏览器),加速安装

2.4 CI 时间优化

优化策略效果复杂度
依赖缓存跳过 npm install低(setup-node 内置)
构建缓存复用上次的 .next/cache
并行 jobslint、test、build 同时跑
取消过时运行新 push 取消旧 CI
只测试变更根据文件变更决定跑哪些测试
Turborepo Remote CacheMonorepo 构建缓存共享
自托管 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
加速 ReviewReviewer 不需要本地拉代码构建

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 工作先加列,再改代码,最后删旧列
分步执行大迁移拆成多个小迁移重命名列 = 加新列 + 复制数据 + 删旧列
避免锁表大表操作用 CONCURRENTLYCREATE INDEX CONCURRENTLY
有回滚方案每个 up 迁移都有对应的 downDrizzle/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 TestsPR + merge to main1-3min
E2E TestsPR(关键路径)3-10min较高(Flaky)

5.2 测试并行化

GitHub Actions 支持矩阵策略来并行运行测试:

  e2e:
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - run: pnpm test:e2e --shard=${{ matrix.shard }}/4

4 个 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
          fi

6.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

自动化依赖更新:

工具特点推荐场景
DependabotGitHub 内置、简单小项目
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 加密、最小权限、分支保护、自动化依赖更新