Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

前端部署完全指南

本教程参考 山月 · 前端部署十五章 体系编写,涵盖从静态资源服务到 CI/CD 完整流程,帮助前端工程师掌握部署技能。

目录

  1. 静态资源服务
  2. Docker 部署
  3. CI/CD 流程
  4. 进阶主题

1. 静态资源服务

前端部署的本质,就是对静态资源的服务。无论 React/Vue 构建产物,还是纯 HTML 页面,都需要通过 HTTP 服务器对外提供访问。

1.1 最简静态服务器

不使用任何框架,用 Node.js 几行代码即可搭建静态服务器:

// server-fs.js
const http = require('http')
const fs = require('fs')
const path = require('path')

const server = http.createServer((req, res) => {
  const filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url)
  const ext = path.extname(filePath)
  const contentTypes = {
    '.html': 'text/html',
    '.js': 'application/javascript',
    '.css': 'text/css',
    '.png': 'image/png',
    '.json': 'application/json',
  }
  fs.readFile(filePath, (err, data) => {
    if (err) {
      res.writeHead(404)
      res.end('Not Found')
      return
    }
    res.writeHead(200, { 'Content-Type': contentTypes[ext] || 'text/plain' })
    res.end(data)
  })
})

server.listen(3000, () => console.log('Server running on port 3000'))

运行:

node server-fs.js
# 或者使用 serve 工具
npx serve .

但这种方式只适合本地开发,生产环境需要 Nginx。

1.2 Nginx 基本配置

Nginx 是生产环境最常用的静态资源服务器,具备高性能、高并发、占用资源少等特点。

安装 Nginx

# macOS
brew install nginx

# Ubuntu/Debian
sudo apt install nginx

# Docker (推荐,快速体验)
docker run -d --rm -p 3000:80 nginx:alpine

最简配置文件

# deploy/nginx.conf
server {
    listen 80;
    server_name localhost;

    # 静态文件目录
    root /usr/share/nginx/html;
    index index.html;

    location / {
        # 尝试读取文件,未找到则返回 index.html (SPA 需要)
        try_files $uri $uri/ /index.html;
    }
}

启动服务:

# Docker 方式
docker run -d --rm -p 3000:80 \
  -v $(pwd)/dist:/usr/share/nginx/html \
  -v $(pwd)/nginx.conf:/etc/nginx/conf.d/default.conf \
  nginx:alpine

# 验证配置语法
nginx -t

# 重载配置
nginx -s reload

1.3 静态资源缓存策略

缓存是前端部署最重要的性能优化手段之一。合理设置缓存,可大幅减少重复请求、加快页面加载。

Cache-Control 响应头

策略 配置 适用场景
不缓存 Cache-Control: no-cache, no-store, must-revalidate HTML 文件(频繁更新)
协商缓存 Cache-Control: no-cache JS/CSS(配合 ETag)
强缓存 Cache-Control: public, max-age=31536000 静态资源(带 hash 哈希)

Nginx 配置示例:

server {
    root /usr/share/nginx/html;
    expires 1y;  # 静态资源缓存 1 年(前提是文件名带 hash}

ETag 配合协商缓存

Nginx 默认开启 ETag,会根据文件内容生成唯一标识:

# 请求示例
curl -I http://localhost/index.js
# ETag: "abc123"
# Cache-Control: no-cache

# 再次请求,带上 ETag
curl -I -H "If-None-Match: \"abc123\"" http://localhost/index.js
# 命中协商缓存,返回 304 Not Modified

最佳缓存策略:内容 hash + 长期缓存

构建产物文件名带上内容 hash(如 app.abc123.js),设置 max-age=31536000

# 针对带 hash 的静态资源(JS/CSS/图片),设置一年缓存
location ~* \.[a-f0-9]{8,}\.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
}

# HTML 必须每次拉取最新版本
location ~* \.html$ {
    expires -1;
    add_header Cache-Control "no-cache, no-store, must-revalidate";
}

山月金句:静态资源加 hash、接口加版本、HTML 文件不加缓存,这是前端缓存的三大原则。

1.4 SPA 路由配置

单页应用(SPA)所有路由都由前端控制,Nginx 需要将所有路径重定向到 index.html

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

    # SPA 路由支持:所有路径回退到 index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API 请求代理(如果前后端同域部署)
    location /api/ {
        proxy_pass http://backend:8080/;
    }
}

关键点try_files $uri $uri/ /index.html 是 SPA 部署的核心配置,确保刷新页面不会 404。


2. Docker 部署

Docker 容器化是现代部署的基础技能,核心价值:"Build Once, Run Anywhere"

2.1 Dockerfile 编写

单阶段构建(简单场景)

适用于纯静态资源,无需构建步骤:

# static.Dockerfile
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/conf.d/default.conf

多阶段构建(推荐,用于 SPA)

分离构建层与运行时层,大幅减小镜像体积:

# Dockerfile (多阶段构建)
# ---- 构建阶段 ----
FROM node:18-alpine AS builder

WORKDIR /code
# 分离 package.json,最大限度利用 Docker 层缓存
COPY package.json yarn.lock ./
RUN yarn install

COPY . .
RUN npm run build

# ---- 运行阶段 ----
FROM nginx:alpine
# 从构建阶段复制产物
COPY --from=builder /code/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

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

构建并运行:

docker build -t my-app .
docker run -d --rm -p 3000:80 my-app

2.2 Docker Compose 本地开发与生产

docker-compose.yaml 是 Docker Compose 的配置文件,可同时管理多个容器。

本地开发环境

# docker-compose.yaml
version: "3"
services:
  # 前端开发服务器
  dev:
    image: node:18-alpine
    working_dir: /app
    volumes:
      - .:/app
    ports:
      - "5173:5173"
    command: npm run dev -- --host

  # 模拟后端 API
  api:
    image: express:alpine
    ports:
      - "3000:3000"
    volumes:
      - ./api:/app
    command: node index.js
# 启动所有服务
docker-compose up

# 后台运行
docker-compose up -d

# 查看日志
docker-compose logs -f

生产环境

# docker-compose.prod.yaml
version: "3"
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 10s
      retries: 3
# 生产环境启动
docker-compose -f docker-compose.prod.yaml up -d --build

2.3 镜像优化(减小体积 & 层缓存)

优化技巧 1:利用层缓存

把不常变化的依赖层分离出来,优先复制:

# ✅ 正确:先复制依赖文件,安装后再复制源码
COPY package.json yarn.lock ./
RUN yarn install
COPY . .

# ❌ 错误:每次代码变更都会导致依赖重新安装
COPY . .
RUN yarn install

优化技巧 2:使用更小的基础镜像

镜像 大小 适用场景
node:18-alpine ~180MB Node.js 构建
nginx:alpine ~30MB 静态资源服务
node:18-slim ~900MB 需要更多 GNU 工具

优化技巧 3:合并 RUN 指令

减少镜像层数,每层都会占用存储空间:

# ✅ 合并多个 RUN,减少层数
RUN apt-get update && \
    apt-get install -y curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# ❌ 分开写会产生多层
RUN apt-get update
RUN apt-get install -y curl

优化技巧 4:使用 .dockerignore

排除不需要打包进镜像的文件:

node_modules
.git
dist
*.log
.env*
!.env.example

2.4 完整多阶段 Dockerfile 示例

以下是一个生产级 SPA 完整配置:

# ---- 阶段一:构建 ----
FROM node:18-alpine AS builder

WORKDIR /app

# 利用 Docker 层缓存:依赖层优先复制
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

COPY . .

# 构建时注入环境变量
ARG VITE_API_BASE_URL
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL

RUN npm run build

# ---- 阶段二:运行 ----
FROM nginx:alpine

# 从构建阶段复制产物
COPY --from=builder /app/dist /usr/share/nginx/html

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

# 安全加固:非 root 用户
RUN chown -R nginx:nginx /usr/share/nginx/html && \
    chmod -R 755 /usr/share/nginx/html

USER nginx

EXPOSE 80

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

构建命令:

docker build \
  --build-arg VITE_API_BASE_URL=https://api.example.com \
  -t my-spa-app:latest .

3. CI/CD 流程

CI/CD(持续集成 / 持续部署)是现代开发标配,核心目标:代码提交后自动构建、自动测试、自动部署

3.1 GitHub Actions 基本配置

GitHub Actions 是 GitHub 内置的 CI/CD 工具,免费额度充足(2000分钟/月)。

基础概念

  • Workflow:一个 YAML 配置文件,定义一次 CI/CD 流程
  • Job:一组 Step,可并行或串行执行
  • Step:具体步骤(Checkout 代码、安装依赖、运行命令等)
  • Action:可复用的 Step 单元

最简示例

# .github/workflows/ci.yaml
name: CI

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

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

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test

3.2 构建 → 测试 → 部署完整流程

以下是一个包含构建、测试、Docker 构建、部署的完整 Workflow:

# .github/workflows/deploy.yaml
name: Build, Test & Deploy

on:
  push:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ---- Job 1: 构建与测试 ----
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
          cache-dependency-path: yarn.lock

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Lint
        run: npm run lint

      - name: Run tests
        run: npm run test:ci

      - name: Build
        run: npm run build

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 7

  # ---- Job 2: 构建 Docker 镜像 ----
  build-image:
    needs: build-and-test
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
    steps:
      - uses: actions/checkout@v4

      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=
            type=raw,value=latest

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ---- Job 3: 部署到服务器 ----
  deploy:
    needs: build-image
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull ${{ needs.build-image.outputs.image-tag }}
            docker-compose -f docker-compose.prod.yaml up -d
            docker image prune -f

3.3 环境变量与 Secrets 管理

环境变量分类

类型 设置位置 示例
构建时变量 envarg VITE_API_BASE_URL
运行时变量 docker -e.env 数据库连接串
敏感信息 GitHub Secrets SECRET_KEY

GitHub Secrets 配置

  1. 进入仓库 → SettingsSecrets and variablesActions
  2. 添加以下 Secrets:
    • SERVER_HOST:服务器 IP 或域名
    • SERVER_USER:SSH 用户名
    • SERVER_SSH_KEY:私钥(注意换行符)

在 Workflow 中使用 Secrets

- name: Deploy
  env:
    # 直接注入环境变量
    API_SECRET: ${{ secrets.API_SECRET }}
  run: |
    curl -X POST https://api.example.com/deploy \
      -H "Authorization: Bearer $API_SECRET"

构建时注入环境变量

- name: Build
  run: npm run build
  env:
    VITE_API_BASE_URL: ${{ vars.VITE_API_BASE_URL }}
    VITE_VERSION: ${{ github.sha }}

3.4 自动回滚方案

生产环境出问题需要快速回滚,以下是几种回滚策略:

策略 1:基于镜像标签的回滚

# 回滚到上一个稳定版本
deploy:
  runs-on: ubuntu-latest
  steps:
    - name: Rollback to previous image
      run: |
        docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:stable
        docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:stable my-app:latest
        docker-compose -f docker-compose.prod.yaml up -d

策略 2:蓝绿部署

# 同时启动蓝绿两套环境,通过切换 nginx upstream 实现瞬时切换
services:
  blue:
    image: my-app:blue
    networks:
      - app-net

  green:
    image: my-app:green
    networks:
      - app-net

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx-switch.sh:/switch.sh
    networks:
      - app-net

策略 3:GitHub Actions 自动回滚

# .github/workflows/rollback.yaml
name: Rollback

on:
  workflow_dispatch:
    inputs:
      tag:
        description: 'Image tag to rollback to'
        required: true

jobs:
  rollback:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.event.inputs.tag }}
            docker-compose -f docker-compose.prod.yaml up -d
            echo "Rolled back to ${{ github.event.inputs.tag }}"

山月经验:生产部署一定要有回滚方案,宁可回滚到上一个稳定版本,也不要在线上排查问题。记住:Deploy Fast, Rollback Faster。


4. 进阶主题

4.1 Nginx + Docker 配合

生产环境最常见的部署方式:Docker 运行 Nginx 容器

完整 docker-compose.prod.yaml

version: "3"
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    volumes:
      # 挂载 SSL 证书(生产环境)
      - ./certs:/etc/nginx/certs:ro
      # 日志目录
      - nginx_logs:/var/log/nginx
    environment:
      - TZ=Asia/Shanghai
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 5s
      retries: 3

  # Nginx 日志收集(可选)
  # nginx_logs:
  #   driver: none

生产 Nginx 配置

# nginx.conf
# 运行用户(非 root)
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # 日志格式
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;

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

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

        # 强制跳转 HTTPS(生产环境)
        # return 301 https://$server_name$request_uri;

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

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

        # HTML 不缓存
        location ~* \.html$ {
            expires -1;
            add_header Cache-Control "no-cache, no-store, must-revalidate";
        }

        # 静态资源合并(雪碧图等)
        location /assets/ {
            expires 30d;
            add_header Cache-Control "public";
        }
    }

    # HTTPS 配置(生产环境)
    # server {
    #     listen 443 ssl http2;
    #     server_name example.com;
    #
    #     ssl_certificate /etc/nginx/certs/example.com.crt;
    #     ssl_certificate_key /etc/nginx/certs/example.com.key;
    #     ssl_session_cache shared:SSL:10m;
    #     ssl_session_timeout 10m;
    #
    #     location / {
    #         try_files $uri $uri/ /index.html;
    #     }
    # }
}

4.2 PM2 部署 Node.js 应用

PM2 是 Node.js 进程管理器,适合部署 SSR 应用、API 服务、BFF 层等。

安装与使用

# 全局安装
npm install -g pm2

# 启动应用
pm2 start server.js --name my-api

# 查看状态
pm2 list

# 查看日志
pm2 logs my-api

# 重启
pm2 restart my-api

PM2 配置文件 ecosystem.config.js

// ecosystem.config.js
module.exports = {
  apps: [
    {
      name: 'my-api',
      script: 'server.js',
      instances: 1,           // 或 'max' 利用多核
      exec_mode: 'cluster',  // 集群模式
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      // 资源限制
      max_memory_restart: '500M',
      // 日志
      log_file: './logs/combined.log',
      error_file: './logs/error.log',
      out_file: './logs/out.log',
      // 开机自启
      autorestart: true,
      max_restarts: 10,
      min_uptime: '10s',
    },
  ],
}

Docker 中运行 PM2

# pm2.Dockerfile
FROM node:18-alpine

WORKDIR /app
COPY package*.json ./
RUN npm install -g pm2 && npm install

COPY . .

# 启动 PM2(而非 node server.js)
CMD ["pm2-runtime", "ecosystem.config.js"]
# docker-compose.yaml
services:
  api:
    build:
      context: .
      dockerfile: pm2.Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped

4.3 使用 CDN 加速静态资源

CDN(内容分发网络)将静态资源分发到全球节点,用户就近访问,显著提升加载速度。

架构图

用户请求 → CDN 边缘节点(缓存命中)→ 直接返回
    ↓(未命中)
CDN 源站 → 回源拉取资源 → 缓存 → 返回

常用 CDN 服务

服务商 免费额度 特点
Cloudflare 免费版足够个人用 抗 DDoS、自动 HTTPS
Vercel 100GB 带宽/月 零配置,Git 推送即部署
Netlify 100GB 带宽/月 静态站点首选
阿里云 OSS + CDN 按量付费 国内速度快

Vercel/Netlify 零配置部署

只需在项目根目录添加配置文件:

# vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "framework": "vite",
  "routes": [
    { "handle": "filesystem" },
    { "src": "/(.*)", "dest": "/index.html" }
  ]
}

然后连接 Git 仓库,每次 push 自动部署。

自建 CDN 回源配置(Nginx)

upstream cdn_origin {
    server 127.0.0.1:8080;  # 源站地址
}

server {
    listen 80;
    server_name cdn.example.com;

    location / {
        proxy_pass http://cdn_origin;
        proxy_cache my_cache;
        proxy_cache_valid 200 1y;
        proxy_cache_key $uri;
        add_header X-Cache-Status $upstream_cache_status;
    }
}

4.4 部署监控与日志

Nginx 日志分析

# 查看访问最多的 IP
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head 10

# 查看 5xx 错误
awk '$9 ~ /5[0-9]{2}/ {print $0}' /var/log/nginx/access.log

# 查看访问最频繁的路径
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head 10

# 实时查看访问日志
tail -f /var/log/nginx/access.log

PM2 日志管理

# 查看实时日志
pm2 logs my-api --lines 100

# 查看错误日志
pm2 logs my-api --err

# 日志轮转(防止单个文件过大)
pm2 install pm2-logrotate
pm2 set pm2-logrotate:max_size 10M
pm2 set pm2-logrotate:retain 7

Docker 日志

# 查看容器日志
docker logs -f <container_id>

# 查看最近 100 行
docker logs --tail 100 <container_id>

# 带时间戳
docker logs -t <container_id>

# 日志驱动配置(docker-compose.yaml)
services:
  web:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

监控工具推荐

工具 类型 免费额度
Sentry 前端错误监控 5000 次/月
Prometheus + Grafana 基础设施监控 开源免费
UptimeRobot 在线率监控 50 个监控点
Better Uptime 在线率 + 告警 1 个监控点免费

简单健康检查脚本

#!/bin/bash
# health-check.sh
ENDPOINT="http://localhost/"
ALERT_EMAIL="[email protected]"

# 检测 HTTP 状态码
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $ENDPOINT)

if [ "$HTTP_CODE" -ne 200 ]; then
    echo "Health check failed! HTTP code: $HTTP_CODE" | \
    mail -s "[ALERT] Service Down" $ALERT_EMAIL
    # 可选:触发自动回滚
    docker-compose -f docker-compose.prod.yaml pull
    docker-compose -f docker-compose.prod.yaml up -d
fi

附录

A. 常见问题排查

问题 排查方向
502 Bad Gateway Nginx 无法连接后端,检查 upstream 配置和后端服务状态
403 Forbidden 文件权限不足,检查 chmod 755 和 Nginx user 配置
静态资源 404 检查 root 路径配置和文件是否正确 COPY 到容器
刷新页面 404 SPA 未配置 try_files $uri $uri/ /index.html
部署后页面不变 缓存问题,检查 Cache-Control 头或强制刷新 Ctrl+Shift+R

B. 部署检查清单

  • 构建产物中 HTML 文件设置 Cache-Control: no-cache
  • 静态资源(JS/CSS/图片)带 hash,max-age=31536000
  • Nginx 配置 try_files 支持 SPA 路由
  • Docker 镜像使用多阶段构建,体积最小化
  • 生产环境配置健康检查 healthcheck
  • Secrets 敏感信息使用 GitHub Secrets 管理
  • 部署前确认有回滚方案
  • 监控告警已配置

C. 参考资料