前端 CI/CD 流程自动化:从代码提交到生产部署的完整指南
"代码写完了,怎么上线?" 这个问题的答案,区分了业余项目和专业工程。一套成熟的 CI/CD 流程,能让团队从繁琐的手动操作中解放出来,专注于创造价值。
为什么前端需要 CI/CD
没有 CI/CD 的日常
周五下午 5:30
产品经理: "这个 Bug 修好了吗?能发版吗?"
开发者: "修好了,我本地测过了"
周五下午 5:45
开发者手动执行: npm run build
开发者: "构建完成,我传到服务器上..."
周五下午 6:00
线上出现白屏
开发者: "???我本地明明是好的啊"
这个场景你一定不陌生。问题出在哪?
- "我本地是好的":本地环境与生产环境不一致
- 手动构建:容易遗漏步骤,比如忘记执行 lint
- 直接发版:没有经过自动化测试验证
- 单点故障:只有一个人知道怎么发版
有了 CI/CD 的日常
周五下午 5:30
开发者合并 PR
CI 自动执行:
✓ ESLint 检查
✓ TypeScript 类型检查
✓ 单元测试 (覆盖率 85%)
✓ E2E 测试
✓ 构建产物
✓ 部署到预发环境
✓ 自动通知产品经理验收
验收通过后,一键发布生产环境
CI/CD 流水线设计
一条完整的前端 CI/CD 流水线通常包含以下阶段:
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 代码 │ → │ 检查 │ → │ 测试 │ → │ 构建 │ → │ 部署 │
│ 提交 │ │ Lint │ │ Test │ │ Build │ │ Deploy │
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
│ │ │ │ │
Commit ESLint Unit Bundle Staging
Push TSC E2E Optimize Production
Prettier Coverage Assets
GitHub Actions 实战
GitHub Actions 是目前最流行的 CI/CD 工具之一,与 GitHub 无缝集成,配置简单但功能强大。
基础配置
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
# 设置并发策略,避免同一分支重复运行
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm lint
- name: Run TypeScript
run: pnpm type-check
test:
name: Unit Tests
runs-on: ubuntu-latest
needs: lint # 依赖 lint job
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Run tests with coverage
run: pnpm test:coverage
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
fail_ci_if_error: true
e2e:
name: E2E Tests
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Install Playwright
run: pnpm exec playwright install --with-deps
- name: Build application
run: pnpm build
- name: Run E2E tests
run: pnpm test:e2e
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
build:
name: Build
runs-on: ubuntu-latest
needs: [test, e2e]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
多环境部署配置
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches:
- main # 生产环境
- develop # 开发环境
workflow_dispatch: # 支持手动触发
inputs:
environment:
description: 'Deploy environment'
required: true
default: 'staging'
type: choice
options:
- staging
- production
jobs:
deploy-staging:
name: Deploy to Staging
if: github.ref == 'refs/heads/develop'
runs-on: ubuntu-latest
environment:
name: staging
url: https://staging.example.com
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Build for staging
run: pnpm build
env:
VITE_API_URL: ${{ vars.STAGING_API_URL }}
VITE_ENV: staging
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
working-directory: ./
deploy-production:
name: Deploy to Production
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Build for production
run: pnpm build
env:
VITE_API_URL: ${{ vars.PRODUCTION_API_URL }}
VITE_ENV: production
- name: Deploy to CDN
run: |
# 上传静态资源到 OSS/S3
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}/ \
--cache-control "public, max-age=31536000, immutable" \
--exclude "*.html"
# HTML 文件使用较短的缓存时间
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}/ \
--cache-control "public, max-age=0, must-revalidate" \
--include "*.html"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ap-northeast-1
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} \
--paths "/*"
缓存优化
CI/CD 流水线中,依赖安装往往是最耗时的环节。合理的缓存策略能大幅提升构建速度。
依赖缓存
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js with cache
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm' # 自动缓存 pnpm store
# 或者手动配置缓存
- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
构建缓存
- name: Cache Next.js build
uses: actions/cache@v4
with:
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
${{ runner.os }}-nextjs-
- name: Cache Turbo
uses: actions/cache@v4
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
分支策略与保护规则
Git Flow 分支模型
main (生产环境)
│
├── develop (开发环境)
│ │
│ ├── feature/user-auth
│ ├── feature/payment
│ └── feature/dashboard
│
├── release/v1.2.0 (预发布)
│
└── hotfix/critical-bug (紧急修复)
分支保护规则配置
# .github/branch-protection.yml (需要配合 GitHub App)
branches:
- name: main
protection:
required_pull_request_reviews:
required_approving_review_count: 2
dismiss_stale_reviews: true
require_code_owner_reviews: true
required_status_checks:
strict: true
contexts:
- "lint"
- "test"
- "e2e"
- "build"
enforce_admins: true
required_linear_history: true
allow_force_pushes: false
allow_deletions: false
- name: develop
protection:
required_pull_request_reviews:
required_approving_review_count: 1
required_status_checks:
strict: true
contexts:
- "lint"
- "test"
自动化版本管理
使用 Changesets 或 semantic-release 自动管理版本号和 CHANGELOG。
Changesets 配置
# .github/workflows/release.yml
name: Release
on:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
publish: pnpm release
version: pnpm version-packages
commit: 'chore: release packages'
title: 'chore: release packages'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Changeset 配置文件
// .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": [
"@changesets/changelog-github",
{ "repo": "your-org/your-repo" }
],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
通知与监控
Slack 通知
- name: Notify Slack on success
if: success()
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "✅ 部署成功",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* 部署成功\n环境: ${{ github.ref_name }}\n提交: `${{ github.sha }}`\n作者: ${{ github.actor }}"
}
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "查看部署" },
"url": "${{ steps.deploy.outputs.url }}"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"text": "❌ 部署失败",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ github.repository }}* 部署失败\n分支: ${{ github.ref_name }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|查看日志>"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
飞书通知
- name: Notify Feishu
if: always()
run: |
STATUS="${{ job.status }}"
if [ "$STATUS" = "success" ]; then
COLOR="green"
EMOJI="✅"
else
COLOR="red"
EMOJI="❌"
fi
curl -X POST "${{ secrets.FEISHU_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "interactive",
"card": {
"header": {
"title": { "tag": "plain_text", "content": "'"$EMOJI"' 前端部署通知" },
"template": "'"$COLOR"'"
},
"elements": [
{
"tag": "div",
"fields": [
{ "is_short": true, "text": { "tag": "lark_md", "content": "**项目**\n${{ github.repository }}" }},
{ "is_short": true, "text": { "tag": "lark_md", "content": "**分支**\n${{ github.ref_name }}" }},
{ "is_short": true, "text": { "tag": "lark_md", "content": "**状态**\n'"$STATUS"'" }},
{ "is_short": true, "text": { "tag": "lark_md", "content": "**作者**\n${{ github.actor }}" }}
]
}
]
}
}'
安全最佳实践
密钥管理
# 使用 GitHub Environments 管理不同环境的密钥
jobs:
deploy:
environment: production # 引用 production 环境的密钥
steps:
- name: Deploy
run: ./deploy.sh
env:
# 从环境中获取密钥
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
依赖安全扫描
# .github/workflows/security.yml
name: Security Scan
on:
push:
branches: [main, develop]
schedule:
- cron: '0 0 * * 1' # 每周一扫描
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run npm audit
run: pnpm audit --audit-level=high
continue-on-error: true
- name: Run Snyk
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
性能监控集成
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v10
with:
urls: |
https://staging.example.com/
https://staging.example.com/products
https://staging.example.com/about
budgetPath: ./lighthouse-budget.json
uploadArtifacts: true
temporaryPublicStorage: true
- name: Bundle Size Check
uses: siddharthkp/bundlesize@v2
env:
BUNDLESIZE_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
// lighthouse-budget.json
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "total", "budget": 500 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 10 }
]
}
]
完整的 Monorepo CI/CD
对于 Monorepo 项目,使用 Turborepo 可以大幅提升构建效率。
# .github/workflows/ci-monorepo.yml
name: CI (Monorepo)
on:
push:
branches: [main]
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # Turbo 需要比较 commit
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- uses: pnpm/action-setup@v2
with:
version: 8
- run: pnpm install --frozen-lockfile
# 使用 Turbo 的远程缓存
- name: Build with Turbo
run: pnpm turbo run build lint test --filter="...[HEAD^1]"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
结语
CI/CD 不仅仅是技术实践,更是团队协作方式的升级。一条设计良好的流水线:
- 提升信心:每次提交都经过自动化验证
- 加速交付:从代码到生产只需要几分钟
- 降低风险:问题在早期就被发现
- 解放人力:不再需要人工执行重复操作
从今天开始,为你的项目配置 CI/CD 吧。投入的时间会在未来成倍地回报给你。
"如果一件事值得做,就值得自动化。" —— 某位不愿透露姓名的 DevOps 工程师


