部署React应用

部署是将你的React应用交付给用户的最后一步,也是最重要的一步。正确的部署策略能确保应用的高可用性、高性能和安全性。

部署流程总览

1
开发

编写和测试代码

2
构建

生产环境构建

3
测试

自动化测试验证

4
部署

发布到生产环境

5
监控

性能监控和优化

部署前准备

1. 代码优化

// package.json - 优化脚本
{
  "name": "my-react-app",
  "version": "1.0.0",
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "build:prod": "npm run build && npm run optimize",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "optimize": "npm run optimize:images && npm run optimize:bundle",
    "optimize:images": "imagemin src/images/* --out-dir=build/images",
    "optimize:bundle": "webpack-bundle-analyzer build/stats.json",
    "predeploy": "npm run build",
    "deploy": "gh-pages -d build"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
// 环境配置文件
// .env.production
REACT_APP_API_URL=https://api.example.com
REACT_APP_SENTRY_DSN=https://xxx@sentry.io/xxx
REACT_APP_GA_TRACKING_ID=UA-XXXXX-Y
REACT_APP_VERSION=$npm_package_version

// .env.development
REACT_APP_API_URL=http://localhost:3001
REACT_APP_DEBUG=true

// 在代码中使用
const apiUrl = process.env.REACT_APP_API_URL;
const isProduction = process.env.NODE_ENV === 'production';

// 条件引入
if (isProduction) {
  const Sentry = require('@sentry/react');
  Sentry.init({ dsn: process.env.REACT_APP_SENTRY_DSN });
}

2. 生产环境构建

# 基础构建命令
npm run build

# 带分析功能的构建
npm run build -- --profile --stats

# 使用环境变量构建
REACT_APP_API_URL=https://api.example.com npm run build

# 检查构建输出
ls -la build/
# 输出:
# asset-manifest.json
# index.html
# robots.txt
# static/
#   css/
#   js/
#   media/

# 本地测试构建结果
npx serve -s build -l 3000
# 或
npm install -g serve
serve -s build
构建优化技巧:
  • 使用--profile标志生成性能分析数据
  • 添加--stats生成Webpack统计文件用于分析
  • 确保.env.production文件中的环境变量正确配置
  • 检查构建输出中的bundle大小

3. 构建产物分析

# 安装分析工具
npm install --save-dev source-map-explorer webpack-bundle-analyzer

# package.json中添加脚本
"scripts": {
  "analyze": "source-map-explorer build/static/js/*.js",
  "analyze:bundle": "webpack-bundle-analyzer build/stats.json"
}

# 生成分析报告
npm run build -- --stats
npm run analyze:bundle

# 分析结果示例
# 📦 总大小: 1.2MB (gzip: 350KB)
# ├── React + ReactDOM: 120KB
# ├── 业务代码: 450KB
# ├── 第三方库: 550KB
# └── 样式: 80KB
首次内容绘制

1.2s

目标: < 1.8s
JS Bundle大小

450KB

目标: < 500KB
CSS Bundle大小

80KB

目标: < 100KB
资源请求数

15

目标: < 20

部署平台对比

推荐

Vercel

React官方推荐的部署平台,专为前端优化

  • ✓ 自动HTTPS和CDN
  • ✓ 自动CI/CD
  • ✓ 预览部署
  • ✓ 边缘网络
  • ✓ 免费套餐
# 部署命令
npm i -g vercel
vercel
# 或直接推送代码
git push
流行

Netlify

功能丰富的静态站点部署平台

  • ✓ 表单处理
  • ✓ 身份验证
  • ✓ 无服务器函数
  • ✓ 分支部署
  • ✓ 免费套餐
# 安装Netlify CLI
npm i -g netlify-cli
netlify deploy --prod
# 或连接Git仓库自动部署
企业级

AWS S3 + CloudFront

企业级可扩展部署方案

  • ✓ 全球CDN
  • ✓ 高可用性
  • ✓ 自定义域名
  • ✓ 按使用付费
  • ✓ 企业级安全
# AWS CLI配置
aws s3 sync build/ s3://bucket-name
aws cloudfront create-invalidation \
  --distribution-id DISTRIBUTION_ID \
  --paths "/*"
容器化

Docker容器

跨平台一致的部署环境

  • ✓ 环境一致性
  • ✓ 易于扩展
  • ✓ 多种部署方式
  • ✓ 微服务友好
  • ✓ 开发生产一致
# Dockerfile示例
FROM nginx:alpine
COPY build/ /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
传统

传统服务器

完全控制的自托管方案

  • ✓ 完全控制权
  • ✓ 自定义配置
  • ✓ 数据私有
  • ✓ 成本可控
  • ✓ 技术要求高
# Nginx配置示例
server {
    listen 80;
    server_name example.com;
    root /var/www/react-app;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }
}
自动化

CI/CD自动化

自动化测试和部署流程

  • ✓ 自动化测试
  • ✓ 自动部署
  • ✓ 回滚机制
  • ✓ 质量门禁
  • ✓ 团队协作
# GitHub Actions示例
name: Deploy
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build

部署演示控制台

🚀 部署控制台已就绪
选择上面的操作开始部署流程

详细部署配置

1. Docker部署配置

# Dockerfile - 多阶段构建优化
# 第一阶段:构建阶段
FROM node:16-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# 第二阶段:生产阶段
FROM nginx:alpine

# 复制构建产物
COPY --from=builder /app/build /usr/share/nginx/html

# 复制自定义nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 添加健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

# docker-compose.yml
version: '3.8'
services:
  react-app:
    build: .
    ports:
      - "80:80"
    environment:
      - NODE_ENV=production
      - REACT_APP_API_URL=https://api.example.com
    volumes:
      - ./logs:/var/log/nginx
    restart: unless-stopped
    networks:
      - webnet

networks:
  webnet:
    driver: bridge

# nginx.conf
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html index.htm;

    # Gzip压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript
               application/javascript application/xml+rss;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # React路由支持
    location / {
        try_files $uri $uri/ /index.html;
    }

    # 静态资源缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "healthy\n";
    }
}

2. CI/CD配置示例

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test -- --coverage --watchAll=false

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '16'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build
        env:
          REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}
          REACT_APP_VERSION: ${{ github.sha }}

      - name: Analyze bundle size
        uses: preactjs/compressed-size-action@v2
        with:
          pattern: "build/static/**/*.{js,css}"
          repo-token: ${{ secrets.GITHUB_TOKEN }}

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - uses: actions/checkout@v3

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID}}
          vercel-project-id: ${{ secrets.PROJECT_ID}}
          vercel-args: '--prod'

      - name: Notify deployment
        run: |
          curl -X POST -H 'Content-type: application/json' \
          --data '{"text":"🚀 Production deployment completed!\nVersion: ${{ github.sha }}\nURL: https://app.example.com"}' \
          ${{ secrets.SLACK_WEBHOOK_URL }}

# Azure DevOps Pipeline
trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

variables:
  npm_config_cache: $(Pipeline.Workspace)/.npm

stages:
- stage: Build
  displayName: Build stage
  jobs:
  - job: Build
    displayName: Build
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '16.x'
    - script: |
        npm ci
        npm run build
      displayName: 'npm install and build'

- stage: Deploy
  displayName: Deploy stage
  dependsOn: Build
  jobs:
  - deployment: Deploy
    displayName: Deploy
    environment: 'production'
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureWebApp@1
            inputs:
              azureSubscription: 'Azure Subscription'
              appName: 'my-react-app'
              package: '$(System.DefaultWorkingDirectory)/build'

3. 环境变量管理

// 环境变量配置最佳实践
// .env.production
REACT_APP_API_URL=https://api.production.com
REACT_APP_SENTRY_DSN=https://xxx@sentry.io/xxx
REACT_APP_GA_TRACKING_ID=UA-XXXXX-Y
REACT_APP_VERSION=$npm_package_version
REACT_APP_BUILD_DATE=$npm_package_build_date

// .env.staging
REACT_APP_API_URL=https://api.staging.com
REACT_APP_SENTRY_DSN=https://xxx@sentry.io/xxx
REACT_APP_ENVIRONMENT=staging

// .env.development
REACT_APP_API_URL=http://localhost:3001
REACT_APP_DEBUG=true
REACT_APP_MOCK_API=true

// 环境配置文件
// src/config/env.js
const env = {
  apiUrl: process.env.REACT_APP_API_URL,
  environment: process.env.NODE_ENV,
  isDevelopment: process.env.NODE_ENV === 'development',
  isProduction: process.env.NODE_ENV === 'production',
  sentryDsn: process.env.REACT_APP_SENTRY_DSN,
  gaTrackingId: process.env.REACT_APP_GA_TRACKING_ID,
  version: process.env.REACT_APP_VERSION || '1.0.0',

  // 验证必需的环境变量
  validate() {
    const required = ['REACT_APP_API_URL'];
    const missing = required.filter(key => !process.env[key]);

    if (missing.length > 0) {
      throw new Error(`缺少必需的环境变量: ${missing.join(', ')}`);
    }
  }
};

// 在应用启动时验证
env.validate();

export default env;

// 使用示例
import env from './config/env';

if (env.isProduction) {
  // 初始化生产环境专用的库
  Sentry.init({ dsn: env.sentryDsn });
  ReactGA.initialize(env.gaTrackingId);
}

// 在组件中使用
function App() {
  return (
    <div>
      <p>API端点: {env.apiUrl}</p>
      <p>环境: {env.environment}</p>
      <p>版本: {env.version}</p>
    </div>
  );
}

部署后监控与优化

监控指标 工具/方法 目标值 告警阈值
页面加载时间 Google Lighthouse, Web Vitals < 3秒 > 5秒
JavaScript错误率 Sentry, LogRocket < 0.1% > 1%
API响应时间 New Relic, Datadog < 200ms > 500ms
服务器状态码 Uptime Robot, Pingdom 100% 200状态码 5xx错误
用户会话时长 Google Analytics, Amplitude > 2分钟 < 30秒
// 性能监控集成示例
// src/monitoring.js
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import ReactGA from 'react-ga4';

export const initMonitoring = () => {
  // 只在生产环境初始化
  if (process.env.NODE_ENV !== 'production') {
    return;
  }

  // 初始化Sentry错误监控
  Sentry.init({
    dsn: process.env.REACT_APP_SENTRY_DSN,
    integrations: [new BrowserTracing()],
    tracesSampleRate: 0.2, // 采样率
    environment: process.env.REACT_APP_ENVIRONMENT || 'production',
    release: process.env.REACT_APP_VERSION,
    beforeSend(event) {
      // 过滤不需要的错误
      if (event.exception?.values?.[0]?.value?.includes('ResizeObserver')) {
        return null;
      }
      return event;
    }
  });

  // 初始化Google Analytics
  if (process.env.REACT_APP_GA_TRACKING_ID) {
    ReactGA.initialize(process.env.REACT_APP_GA_TRACKING_ID);
  }

  // 性能监控
  if ('PerformanceObserver' in window) {
    const observer = new PerformanceObserver((list) => {
      for (const entry of list.getEntries()) {
        console.log('[Performance]', entry.name, entry.duration);

        // 发送到分析服务
        if (entry.duration > 100) { // 超过100ms
          Sentry.addBreadcrumb({
            category: 'performance',
            message: `Slow ${entry.name}: ${entry.duration}ms`,
            level: 'warning'
          });
        }
      }
    });

    observer.observe({ entryTypes: ['measure', 'paint'] });
  }

  // 错误边界集成
  const originalErrorHandler = window.onerror;
  window.onerror = function(message, source, lineno, colno, error) {
    console.error('Global error caught:', { message, source, lineno, colno, error });

    // 发送到Sentry
    Sentry.captureException(error);

    // 调用原始处理器
    if (originalErrorHandler) {
      originalErrorHandler.apply(this, arguments);
    }
  };
};

// 在index.js中调用
import { initMonitoring } from './monitoring';
initMonitoring();

部署检查清单

常见部署问题与解决方案:
  • 白屏问题:检查路由配置、静态文件路径、SPA回退配置
  • API请求404:确认API代理配置、CORS设置、环境变量
  • 样式丢失:检查CSS打包路径、publicPath配置
  • 性能下降:启用Gzip压缩、CDN缓存、代码分割优化
  • 环境变量不生效:重新构建应用、检查.env文件命名

部署示例