tva
← 洞察

将 React 应用部署到生产环境:Docker 与 Traefik 反向代理完整配置

在本地构建 React 应用程序很简单。将其正确部署到生产服务器,才是大多数开发者遇到意外障碍的地方。本指南记录了一次真实的部署调试过程:容器运行、Traefik 标签设置、DNS 解析都看似正常,应用程序却持续返回 404 错误。

本文将介绍在本地构建 React 应用程序并将其部署到生产 Docker 服务器的完整过程,包括正确的反向代理配置、自动化 SSL 证书管理和规范的域名路由。这种方法建立在我们的自托管理念之上,类似于我们此前介绍的自托管 n8n 进行工作流自动化以及构建多租户开发栈,让您全面掌控运维。

传统 React 部署的问题

大多数 React 部署教程跳过了关键的生产细节。您会找到演示 npm run build 并将文件复制到 nginx 的指南,但它们很少涵盖:

配置冲突:

  • 与全局重定向冲突的自定义 HTTP 路由器
  • 导致静默失败的 Traefik 标签语法错误
  • 健康检查中的 IPv6 与 IPv4 绑定问题
  • 缺少服务端口映射而导致的 404 错误

资源管理:

  • 磁盘已满导致容器注册失败
  • 不必要的构建产物造成 Docker 镜像膨胀
  • 低效的缓存策略拖慢部署
  • 内存限制影响构建性能

生产环境准备:

  • 正确的 SSL 证书自动化
  • 零停机部署策略
  • 健康检查配置
  • 日志记录和监控集成

结果就是,即使“一切看起来都正确”,仍要花数小时调试为什么一个在本地正常运行的应用,会在生产环境中返回莫名的 404 错误。

我们使用的工具

先了解这个精简的 React 部署架构中,各个组件分别做什么:

Vite:现代构建工具

Vite 提供极快的开发体验,并优化生产构建。与 Create React App 不同,Vite 在开发阶段利用原生 ES 模块,在生产构建时生成高度优化的打包文件。React 应用构建所需的时间由分钟缩短到秒。

主要优势在于,Vite 自动处理代码分割、摇树优化和资源优化,无需复杂的 webpack 配置,就能得到可用于生产环境的构建产物。

Docker:通过容器化保持一致

Docker 确保 React 应用在开发和生产环境中的运行方式一致。在本地提供应用服务的同一个 nginx 容器,在生产服务器上也会有相同的行为,从而在很大程度上消除典型的“在我的机器上能运行”问题。

可以将 Docker 理解为:把整个应用环境,包括 React 构建文件、nginx 配置和运行时,打包到一个可在任何地方运行的便携容器中。

Traefik:智能反向代理

Traefik 根据域名自动将请求路由到正确的容器化应用,充当智能流量调度器。您无需为每个新应用手动配置复杂的 nginx 或 Apache 规则,Traefik 会读取 Docker 容器的标签,自动设置路由。

我们在多租户 Docker 配置中展示了 Traefik 管理多个客户环境的能力。同样的原理也适用于在单台服务器上管理多个 React 应用。

Traefik 只需少量配置,就能通过 Let’s Encrypt 自动处理 SSL 终止,提供自动服务发现和详细监控。

nginx:生产环境 Web 服务器

nginx 能以出色的性能提供 React 静态构建文件。它是生产环境中提供静态内容的事实标准,能够以极少的资源高效处理数千个并发连接。

理解部署流程

从本地开发到生产环境的完整流程如下:

  1. 本地开发:使用 npm run dev 在本地运行并测试 React 应用
  2. 生产构建:使用 npm run build 生成优化后的静态文件
  3. 容器化:将构建文件打包到 nginx Docker 容器中
  4. 服务器部署:上传容器并在生产服务器上启动
  5. Traefik 注册:自动配置路由并签发 SSL 证书
  6. 健康监控:通过持续健康检查确保可用性

这套流程的优势在于自动化。正确配置后,只需一条命令,就能在 60 秒内完成更新部署。

配置 React 应用

面向生产部署的项目结构

组织 React 项目时,应考虑部署需求:

my-react-app/
├── src/                    # React source code
├── public/                 # Static assets
├── dist/                   # Build output (auto-generated)
├── package.json           # Dependencies
├── vite.config.ts         # Vite configuration
├── Dockerfile             # Container definition
├── nginx.conf             # nginx configuration
└── docker-compose.yml     # Deployment definition

优化 Vite 配置

创建 vite.config.ts,采用针对生产环境优化的配置:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
    sourcemap: false, // Disable in production for security
    minify: 'terser',
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
  server: {
    port: 3000,
    host: true, // Enable network access
  },
})

这个配置会:

  • 分离第三方库,改善缓存效果
  • 压缩代码,减小文件体积
  • 在生产环境中禁用源映射,避免发布原始、未经压缩的源代码
  • 优化代码块拆分,加快加载速度

构建生产版本

构建优化后的生产包:

# Install dependencies
npm install

# Create production build
npm run build

# Verify build output
ls -lh dist/

dist/ 文件夹应包含:

  • index.html——入口文件
  • assets/——压缩后的 JS、CSS 和图片
  • 来自 public/ 的静态文件

创建生产容器

React 的 nginx 配置

React 应用使用客户端路由,需要专门的 nginx 配置。创建 nginx.conf:

server {
    listen 80;
    server_name _;
    
    root /usr/share/nginx/html;
    index index.html;

    # Gzip compression for better performance
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript 
               application/x-javascript application/xml+rss 
               application/javascript application/json;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # SPA: Route all paths to index.html for client-side routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Cache static assets aggressively
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # No cache for HTML to ensure updates are immediate
    location ~* \.html$ {
        expires -1;
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
    }

    # Health check endpoint for monitoring
    location /health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/plain;
    }
}

关键是 try_files $uri $uri/ /index.html,它确保 React Router 在生产环境中正常工作:所有路由都会返回主 index.html 文件。

用于生产环境的 Dockerfile

创建优化后的 Dockerfile:

FROM nginx:alpine

# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Copy production build files
COPY dist/ /usr/share/nginx/html/

EXPOSE 80

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

这里使用 nginx:alpine 构建小型生产镜像,截至 2026 年 10 月,其在 Docker Hub 上的压缩大小约为 29 MB,并包含提供 React 应用服务所需的一切。

Docker Compose 配置

创建 docker-compose.yml,方便部署:

services:
  my-react-app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: my-react-app
    restart: unless-stopped
    networks:
      - proxy
    labels:
      # Enable Traefik
      - "traefik.enable=true"
      
      # Define routing rule
      - "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
      - "traefik.http.routers.myapp.entrypoints=https"
      - "traefik.http.routers.myapp.tls=true"
      - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
      
      # Define service port
      - "traefik.http.services.myapp.loadbalancer.server.port=80"
      
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:80/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

networks:
  proxy:
    external: true

关键配置说明:

许多部署失败都出在 labels 部分。注意,我们没有包含以下内容:

  • 单独的 HTTP 路由器定义
  • 自定义重定向中间件
  • HTTP 入口点配置

原因是 Traefik 的全局配置已经处理了 HTTP→HTTPS 重定向。在我们的配置中,额外添加针对单个服务的自定义 HTTP 路由器与该重定向发生了冲突,导致 404 错误,这正是我们在调试过程中解决的问题。

部署到生产服务器

服务器的前置条件

生产服务器需要满足以下条件:

Docker 环境:

# Verify Docker is installed
docker --version
docker compose --version

# Verify Traefik is running
docker ps | grep traefik

# Verify proxy network exists
docker network ls | grep proxy

如果尚未配置 Traefik,请参阅我们的多租户 Docker 指南,其中详细介绍了 Traefik 的完整配置。

充足的磁盘空间:

# Check available space
df -h /

# You need at least 2-5GB free for Docker operations

DNS 配置:

  • 将 app.yourdomain.com 指向服务器的 IP 地址
  • 等待 DNS 传播,通常需要 5-60 分钟

上传应用

将应用传输到服务器:

# From your local machine
scp -r my-react-app/ user@your-server:/opt/

# Or use rsync for efficient updates
rsync -avz --exclude 'node_modules' \
  my-react-app/ user@your-server:/opt/my-react-app/

构建并启动容器

通过 SSH 登录服务器并部署:

# Navigate to application directory
cd /opt/my-react-app

# Build the Docker image
docker compose build

# Start the container
docker compose up -d

# Verify it's running
docker compose ps

验证部署

检查各项功能是否正常:

# Test container health internally
docker compose exec my-react-app wget -q -O- http://127.0.0.1/health

# Check Traefik routing (wait 30 seconds for SSL)
curl -I https://app.yourdomain.com

# View container logs
docker compose logs -f

curl 命令应返回 HTTP/2 200,表示成功。

常见部署故障与解决方法

配置正确却仍出现 404 错误

现象:容器内部运行正常,Traefik 却返回 HTTP/2 404。

根本原因:同一服务定义了多个路由器,却未正确映射服务端口;或者自定义 HTTP 路由器覆盖了 Traefik 的全局重定向。

解决方法:

# Remove these labels if present:
# - "traefik.http.middlewares.myapp-redirect.redirectscheme.scheme=https"
# - "traefik.http.routers.myapp-http.rule=Host(`app.yourdomain.com`)"
# - "traefik.http.routers.myapp-http.entrypoints=http"
# - "traefik.http.routers.myapp-http.middlewares=myapp-redirect"

# Keep only HTTPS router:
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.myapp.rule=Host(`app.yourdomain.com`)"
  - "traefik.http.routers.myapp.entrypoints=https"
  - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
  - "traefik.http.services.myapp.loadbalancer.server.port=80"

Traefik 的全局 HTTP→HTTPS 重定向在 traefik.yml 中配置,会自动处理 HTTP 流量。在我们的配置中,针对单个服务的自定义 HTTP 路由器造成了冲突。

容器显示不健康状态

现象:docker compose ps 显示容器状态为“unhealthy”。

根本原因:健康检查使用的 localhost 解析为 IPv6 地址 [::1],但 nginx 只监听 IPv4。

解决方法:

healthcheck:
  # Use explicit IPv4 address instead of localhost
  test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:80/health"]

Docker 构建失败并提示“No Space Left”

现象:构建失败,提示磁盘空间不足。

解决方法:

# Check disk usage
df -h /

# Clean Docker system
docker system prune -a -f

# Remove unused images
docker image prune -a -f

# Remove unused volumes
docker volume prune -f

如果磁盘确实已满,使用率超过 95%,就需要释放空间或扩容。Docker 进行镜像层缓存和构建时需要临时空间。

SSL 证书未生成

现象:等待 10+ 分钟后,curl 仍显示自签名证书。

常见原因:

  1. DNS 未正确指向服务器
  2. 无法从互联网访问 80/443 端口
  3. 触发 Let’s Encrypt 的速率限制(每 7 天,同一组主机名最多签发 5 张重复证书,或每个注册域名最多签发 50 张证书)

解决方法:

# Verify DNS resolution
dig app.yourdomain.com

# Test port accessibility
curl -I http://app.yourdomain.com

# Check Traefik logs for ACME errors
docker logs traefik | grep -i acme

# Restart Traefik if needed
docker restart traefik

React Router 在刷新时出现 404 错误

现象:应用首次加载正常,但在 /about 等路由上刷新时返回 404。

根本原因:nginx 配置中缺少 try_files 指令。

解决方法:确保 nginx.conf 包含:

location / {
    try_files $uri $uri/ /index.html;
}

这会让 nginx 为所有路由返回 index.html,由 React Router 在客户端处理路由。

容器已启动,但 Traefik 无法访问

现象:容器运行正常,但 Traefik 返回“Service Unavailable”。

解决方法:

# Verify container is on correct network
docker network inspect proxy

# Check if container is listed
docker inspect my-react-app | grep -A 20 Networks

# Ensure proxy network exists
docker network create proxy

生产环境优化

实现短暂停机部署

仅需短暂中断即可更新应用:

#!/bin/bash
# deploy-update.sh - Low-downtime deployment
set -e

cd /opt/my-react-app

# Pull latest code (from git or updated files)
git pull origin main

# Build new production assets
npm install
npm run build

# Build the new image while the old container keeps serving
docker compose build my-react-app

# Replace the container: Compose stops the old one, then starts the new one
docker compose up -d --no-deps my-react-app

# Traefik routes to the new container once its health check passes

如脚本注释所述,docker compose up 会先停止旧容器,再启动替代容器,而使用固定 container_name 的服务无法并行运行两个实例,因此 nginx 启动期间会有短暂的服务中断。真正的零停机更新需要在 Traefik 后面运行第二个副本,或采用蓝绿部署。

高级健康检查

实施全面的健康监控:

healthcheck:
  test: |
    wget --no-verbose --tries=1 --spider http://127.0.0.1:80/health &&
    wget --no-verbose --tries=1 --spider http://127.0.0.1:80/ 
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 10s

这会同时检查健康端点和应用主路由,确保整个应用都能正确响应。

性能优化

微调 nginx,提升性能:

# Add to nginx.conf
server {
    # ... existing config ...
    
    # Increase buffer sizes for large headers
    client_header_buffer_size 1k;
    large_client_header_buffers 4 8k;
    
    # Enable keep-alive connections
    keepalive_timeout 65;
    keepalive_requests 100;
    
    # Optimize file serving
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
}

资源限制

通过容器资源限制,防止资源耗尽:

services:
  my-react-app:
    # ... existing config ...
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

nginx 提供 React 静态文件所需的资源很少,因此 512 MB 内存和半个 CPU 核心是许多网站的合理起点;在依靠这些资源应对高流量之前,请先进行负载测试。

自托管 Docker 部署的价值

在 Docker 基础设施上自托管 React 应用,让您完全掌控部署流水线,避免供应商锁定。您可以在自己的基础设施上部署不限数量的应用,定制部署过程的每个环节,并与现有自托管服务顺畅集成。

这种方法尤其适合结合我们的多租户 Docker 架构,在同一基础设施上托管多个客户应用,同时实现完整隔离。

自动化与 CI/CD 集成

GitHub Actions 部署

在每次推送时自动部署:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Build React app
        run: |
          npm ci
          npm run build
      
      - name: Deploy to server
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          source: "dist/,Dockerfile,nginx.conf,docker-compose.yml"
          target: "/opt/my-react-app"
      
      - name: Restart container
        uses: appleboy/[email protected]
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/my-react-app
            docker compose up -d --build

GitLab CI/CD 流水线

GitLab 用户可以使用以下配置:

# .gitlab-ci.yml
stages:
  - build
  - deploy

build:
  stage: build
  image: node:18
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

deploy:
  stage: deploy
  image: alpine:latest
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
  script:
    - scp -r dist/ Dockerfile nginx.conf docker-compose.yml $SERVER_USER@$SERVER_HOST:/opt/my-react-app/
    - ssh $SERVER_USER@$SERVER_HOST "cd /opt/my-react-app && docker compose up -d --build"
  only:
    - main

监控生产部署

日志策略

实施全面的日志记录:

# Add to docker-compose.yml
services:
  my-react-app:
    # ... existing config ...
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

高效查看日志:

# Real-time logs
docker compose logs -f my-react-app

# Last 100 lines
docker compose logs --tail=100 my-react-app

# Logs from specific time
docker compose logs --since=2h my-react-app

# Filter for errors only
docker compose logs my-react-app | grep -i error

指标与告警

监控容器健康状态:

#!/bin/bash
# health-check.sh - Regular health monitoring

CONTAINER="my-react-app"
WEBHOOK_URL="your-notification-webhook"

STATUS=$(docker inspect --format='{{.State.Health.Status}}' $CONTAINER)

if [ "$STATUS" != "healthy" ]; then
    curl -X POST $WEBHOOK_URL \
      -H 'Content-Type: application/json' \
      -d "{\"text\":\"⚠️ Container $CONTAINER is $STATUS\"}"
fi

通过 cron 每 5 分钟运行一次此脚本,实现基础监控。

将 React 连接到后端基础设施

React 应用通常需要与后端服务通信,这可以自然地融入自托管基础设施。如果您正在使用n8n 进行工作流自动化或Windmill 运行后端工作流,请在 nginx 配置中设置 API 路由。如下所示,通过同源代理转发 /api 可以避免跨源请求,因此浏览器进行这些调用时无需 CORS 响应头:

# Add to nginx.conf for API proxying
location /api {
    proxy_pass http://your-backend-service:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

当所有服务都在同一个 Docker 网络中时,这种配置就能顺畅运行,具体可参阅我们的多租户架构指南。

针对不同环境构建

不同环境通常需要不同配置:

// vite.config.ts
export default defineConfig(({ mode }) => ({
  plugins: [react()],
  define: {
    'import.meta.env.VITE_API_URL': JSON.stringify(
      mode === 'production' 
        ? 'https://api.yourdomain.com'
        : 'http://localhost:3000'
    ),
  },
  build: {
    outDir: 'dist',
    sourcemap: mode !== 'production',
  },
}))

为不同环境构建:

# Development build
npm run build -- --mode development

# Staging build  
npm run build -- --mode staging

# Production build
npm run build -- --mode production

理解这套配置的实际价值

如果您正在做以下工作,这种部署方法就很有用:

管理多个应用:

  • 在同一基础设施上同时部署 React 应用和后端服务
  • 在所有项目中使用一致的部署流程
  • 与 n8n 和 Windmill 等自托管工具集成

为客户开发:

  • 提供通过 SSL 保护的专业自定义域名
  • 完全掌控基础设施和部署
  • 不受平台限制,避免供应商锁定

学习基础设施:

  • 理解 Docker 容器化的基础知识
  • 掌握 Traefik 反向代理配置
  • 系统地排查生产部署问题

这里记录的配置来自一次真实的调试过程,所描述的问题确实发生过,解决方法也确实奏效。相比理论教程,它的价值在于展示了实际陷阱及其规避方法。

结合我们的多租户 Docker 架构,这套配置为可扩展的自托管应用交付提供了基础,让您完全掌控整个过程。

相关资源

更多自托管基础设施指南,请参阅以下资源:

这些指南从不同角度展示了如何构建自托管基础设施,在完全掌控的同时保持专业标准。

获取专业支持

配置生产级 React 部署,需要考虑许多基础设施问题。虽然我们提供了详尽文档,但每个项目都有独特的需求、现有基础设施限制和特定性能要求。

如果您正在构建用于生产环境的 React 部署基础设施,或需要根据客户交付要求进行定制,我们可以协助:

  • 根据您的工作流定制部署流水线
  • 与现有 CI/CD 系统集成
  • 优化高流量应用的性能
  • 制定多区域部署策略
  • 为团队提供 Docker 和 Traefik 最佳实践培训
  • 持续管理和监控基础设施

通过 tva.sg/contact 联系我们,讨论您的 React 部署需求,并获得专业的实施指导。

无论您正在扩大现有代理服务商的业务、推出新的 SaaS 产品,还是建立企业级客户交付能力,我们都可以帮助您采用自托管、容器化的 React 部署,在保持独立性的同时提供专业成果。

延伸阅读