tva
← 洞察

2026 年 React 部署:不可变构建、Traefik 与零停机发布

一篇面向生产环境的最新后续文章,将厂商文档转化为运行控制、迁移决策和可验证的发布标准。

2026 年发生了哪些变化

我们重新审视这一主题,是因为系统的运行边界已经发生变化。长期有效的原则依然值得保留,但在当前版本下,一些过去的捷径已经无法覆盖当前需求,甚至可能带来风险。本文以截至 2026 年 7 月 14 日可获得的厂商文档为起点,区分客观事实与本地决策,并将每一次配置调整视为受控的生产变更。

当前一手资料

如何使用本次更新

首先阅读一手资料,记录实际部署的版本,并在修改设置前定义可观测的目标结果。应在具有代表性的环境中测试最小且可撤销的改动。命令执行成功并不等于验收通过;服务健康、数据完整性、延迟、安全边界和回滚所需时间才是标准。旧文中仍然有效的诊断顺序会作为运行基础保留在下文,但所有与版本有关的示例都必须对照当前文档复核。下文提到的配置文件和命令见原始指南。

运行基础

在本地构建 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 自动处理代码分割、无用代码消除(tree shaking)和资源优化。无需复杂的 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 静态构建文件。它是生产环境静态内容服务的事实标准,以极少的资源高效处理数千个并发连接。

理解部署流程

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

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

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

配置 React 应用

面向生产环境的项目结构

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

my-react-app/
├── src/                    # React source code
├── public/                 # Static assets
├── dist/                   # Build output (auto-generated)
├── package.json            # Dependencies
├── package-lock.json       # Locked dependency versions (npm ci)
├── 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, // Do not publish source maps in production
    // Minification is on by default (Oxc minifier in Vite 8)
    rolldownOptions: {
      output: {
        // Vite 8 (Rolldown): codeSplitting replaces the manualChunks object form
        codeSplitting: {
          groups: [
            { test: /node_modules[\\/](react|react-dom|scheduler)[\\/]/, name: 'vendor' },
          ],
        },
      },
    },
  },
  server: {
    port: 3000,
    host: true, // Enable network access
  },
})

此配置会:

  • 分离第三方库,改善缓存效果
  • 压缩代码,减小文件体积
  • 在生产环境中禁用 source map,防止代码暴露
  • 优化分块,提高加载速度

构建生产版本

构建经过优化的生产包:

# Install the exact dependency versions from package-lock.json
npm ci
# 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 (a location with its own add_header drops these)
    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";
    }
    # Revalidate HTML on every request so new releases show up immediately
    location ~* \.html$ {
        expires -1;
    }
    # Health check endpoint for monitoring
    location /health {
        access_log off;
        default_type text/plain;
        return 200 "healthy\n";
    }
}

关键是 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 月压缩后大小不足 30MB,却包含提供 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 路由器可能与该重定向发生冲突。

容器状态为 unhealthy

症状: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 仍显示自签名证书。

常见原因:

  • DNS 未正确指向服务器
  • 互联网无法访问 80/443 端口
  • 触发 Let’s Encrypt 速率限制,例如,每组完全相同的主机名每 7 天最多可签发 5 张证书,或每个注册域名每 7 天最多可签发 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 2>&1 | 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

生产环境优化

实现零停机部署

以尽量短的停机时间更新应用。使用 docker compose up 重新创建单个容器仍会短暂中断流量;要实现真正的零停机,必须在旧实例停止前,让 Traefik 后方的第二个健康实例投入运行:

#!/bin/bash
# deploy-update.sh - rebuild and replace the container
# Recreating a single container briefly interrupts traffic (see above)
set -euo pipefail
cd /opt/my-react-app
# Pull latest code (from git or updated files)
git pull origin main
# Build new production assets from the lockfile
npm ci
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,
# and --wait returns once the new container reports healthy
docker compose up -d --no-deps --wait my-react-app

高级健康检查

实施全面的健康监控:

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;

    # 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 静态文件只需很少资源:512MB 内存和半个 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@v7

      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '24'
          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 --wait

GitLab CI/CD 流水线

对于 GitLab 用户:

# .gitlab-ci.yml
stages:
  - build
  - deploy
build:
  stage: build
  image: node:24
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour
deploy:
  stage: deploy
  image: alpine:3.23
  before_script:
    - apk add --no-cache openssh-client
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
    # Trust the server's host key (store `ssh-keyscan your-server` output in SSH_KNOWN_HOSTS)
    - mkdir -p ~/.ssh && echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
  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 --wait"
  rules:
    - if: $CI_COMMIT_BRANCH == "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 配置中正确设置 CORS 和 API 路由:

# Add to nginx.conf, above the server block: set Connection only for WebSocket upgrades
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# Inside the server block. Same-origin proxying: the browser makes no
# cross-origin request, so these API calls need no CORS headers
location /api {
    proxy_pass http://your-backend-service:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;
    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
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

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 架构,这套方案为可扩展的自托管应用交付奠定基础,并让您全面掌控交付过程。

相关资源

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

  • 自托管 n8n 实现工作流自动化:自动化部署和基础设施任务
  • 使用 Docker 自托管 Windmill:另一种工作流自动化平台
  • 构建多租户 Docker 技术栈:面向多应用的完整 Traefik 配置

这些指南从不同角度介绍了如何构建自托管基础设施,在保持专业标准的同时让您全面掌控系统。

获取专业支持

建立可用于生产环境的 React 部署方案,需要考虑许多基础设施因素。虽然我们提供了完整文档,但每个项目都有独特需求、现有基础设施约束和具体性能要求。

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

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

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

从配置调整到运行决策

真正需要确认的,不只是平台能否完成配置,还包括团队能否说明责任归属、发现配置漂移、无需临时摸索即可恢复服务,并证明变更改善了预期结果。我们为每项配置变更配套负责人、基线、回滚路径和复核窗口,使一次性修复成为可持续的运营能力。生产变更记录应包含原因、已审查证据、准确范围,以及有权停止或撤销变更的人员。发布后,将约定指标与基线比较,并把结果保存在变更记录中。若没有预期改善,回滚是合理结果,而非失败;这种纪律比任何单项配置值更重要。同一记录还为下一位运维人员提供可信起点,使后续优化成为有测量依据的决策,而非新一轮猜测。

相关洞察

延伸阅读