Next.js从入门到实战保姆级教程(第十五章):部署运维与 CI/CD

1 阅读8分钟

本系列文章将围绕Next.js技术栈,旨在为AI Agent开发者提供一套完整的客户端侧工程实践指南。

再优秀的应用,若无法稳定运行于生产环境,其价值将大打折扣。本章将系统讲解从代码提交到用户访问的完整部署流程,涵盖多种部署方案、自动化流水线及运维监控策略。

部署的本质是将应用部署至持续在线的服务器环境中。与开发环境相比,生产环境对稳定性、安全性、性能的要求显著提升,需要系统化的工程实践支撑。

一、构建产物解析

执行 next build 命令后,Next.js 生成优化后的生产就绪文件:

npm run build

输出示例:

Route (app)                              Size     First Load JS
┌ ○ /                                    5.2 kB          89.4 kB
├ ○ /about                               1.3 kB          85.5 kB
├ ● /blog                                4.8 kB          89 kB
├ ● /blog/[slug]                         2.1 kB          86.3 kB
└ λ /api/data                            0 B             84.2 kB

○  (Static)   预渲染为静态内容
●  (SSG)      静态生成(使用 generateStaticParams)
λ  (Dynamic)  按需服务端渲染

路由类型说明

符号类型说明适用场景
Static纯静态页面,构建时生成 HTML关于我们、联系方式等
SSG静态生成但依赖动态数据博客列表、产品目录
λDynamic每次请求实时渲染用户仪表板、实时数据

.next/ 目录结构

.next/
├── cache/          # 构建缓存(加速二次构建)
├── server/         # 服务端代码和 HTML 模板
├── static/         # 静态资源(JS/CSS/图片)
└── standalone/     # 独立部署包(需开启 output: 'standalone')

二、方案一:Vercel 部署(推荐)

Vercel 是 Next.js 官方推荐的托管平台,提供零配置、全球 CDN、自动 HTTPS的无缝体验。

1. Vercel 核心优势

graph LR
    Git[Git 仓库<br/>GitHub/GitLab] -->|推送代码| Vercel[Vercel 平台]
    Vercel -->|自动构建| Build[next build]
    Build -->|全球分发| Edge[边缘节点 CDN]
    Edge -->|低延迟访问| User[终端用户]
    
    PR[Pull Request] -->|自动创建| Preview[预览环境]
    Preview --> Review[团队审查]

主要优势:

  1. 零配置部署: 自动识别 Next.js 项目并应用最优配置
  2. 全球 CDN: 静态资源分发至全球边缘节点,降低延迟
  3. Edge Functions: 中间件和 API 路由运行在边缘网络
  4. 预览部署: 每个 PR 自动生成独立预览链接
  5. 免费额度充足: 个人项目和小团队基本无需付费

2. 部署步骤

第一步:连接代码仓库

  1. 访问 vercel.com,使用 GitHub 账号登录
  2. 点击 "New Project"
  3. 选择要部署的 Next.js 仓库
  4. 确认框架检测为 "Next.js"

第二步:配置环境变量

在项目设置 → Environment Variables 中添加:

DATABASE_URL=postgresql://user:pass@host:5432/dbname
NEXTAUTH_SECRET=your-super-secret-key-min-32-chars
NEXTAUTH_URL=https://your-domain.com
RESEND_API_KEY=re_xxxxxxxxxxxx

⚠️ 安全警告: 永远不要将包含敏感信息的 .env.local 提交到 Git 仓库。Vercel 的环境变量在平台单独管理,与代码隔离。

第三步:推送代码触发部署

git add .
git commit -m "feat: 添加新功能"
git push origin main
# Vercel 自动检测推送,开始构建和部署

2. vercel.json 高级配置

通过 vercel.json 实现精细化控制:

{
  "version": 2,
  
  // 指定部署区域
  "regions": ["sin1", "hkg1"],
  
  // 自定义响应头
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Access-Control-Allow-Methods", "value": "GET, POST, PUT, DELETE, OPTIONS" },
        { "key": "X-Frame-Options", "value": "DENY" }
      ]
    }
  ],
  
  // URL 重定向
  "redirects": [
    {
      "source": "/old-blog/:slug",
      "destination": "/blog/:slug",
      "permanent": true
    }
  ],
  
  // URL 重写(反向代理)
  "rewrites": [
    {
      "source": "/api/proxy/:path*",
      "destination": "https://internal-api.example.com/:path*"
    }
  ]
}

三、方案二:Docker 容器化部署

当需要完全掌控基础设施(出于安全、合规或成本考虑),Docker 是首选方案。

1. Docker 核心价值

问题场景: 本地开发环境运行正常,部署到服务器却报错——通常是环境差异导致。

解决方案: Docker 通过容器技术将应用及其运行环境打包,实现"一次构建,到处运行"。

2. 启用 Standalone 模式

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  output: 'standalone',
  // standalone 模式将所有依赖打包至 .next/standalone
  // 不再需要 node_modules,镜像体积大幅减小
};

export default nextConfig;

3. 多阶段 Dockerfile

# Dockerfile

# ==================== 第一阶段:安装依赖 ====================
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

# ==================== 第二阶段:构建应用 ====================
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# ==================== 第三阶段:生产镜像(最小化) ====================
FROM node:22-alpine AS runner
WORKDIR /app

# 创建非 root 用户(安全最佳实践)
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# 复制构建产物
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

多阶段构建优势:

  • 最终镜像仅包含运行时必需文件
  • 镜像体积从 1GB+ 缩减至 ~100MB
  • 显著减少攻击面和传输时间

4. Docker Compose 编排

# docker-compose.yml
version: '3.8'

services:
  # Next.js 应用
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - NEXTAUTH_URL=${NEXTAUTH_URL}
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - app-network

  # PostgreSQL 数据库
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U ${DB_USER}']
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    ports:
      - '80:80'
      - '443:443'
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app
    networks:
      - app-network

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

5. Nginx 反向代理配置

# nginx.conf
events {
  worker_connections 1024;
}

http {
  # 上游 Next.js 服务
  upstream nextjs {
    server app:3000;
    keepalive 32;
  }

  # HTTP → HTTPS 重定向
  server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$host$request_uri;
  }

  # HTTPS 服务器
  server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # 静态资源长缓存(文件名含哈希)
    location /_next/static/ {
      alias /app/.next/static/;
      expires 365d;
      add_header Cache-Control "public, immutable";
    }

    # 公共资源
    location /public/ {
      alias /app/public/;
      expires 30d;
    }

    # 动态内容反向代理
    location / {
      proxy_pass http://nextjs;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header 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;
      proxy_cache_bypass $http_upgrade;
    }
  }
}

四、方案三:Node.js 自托管

适用于拥有 VPS 或云服务器,且希望简化部署流程的场景,通常使用PM2来管理应用。

1. PM2 进程管理

PM2 是 Node.js 最流行的进程管理器,提供自动重启、集群模式、日志管理等功能。

# 全局安装 PM2
npm install -g pm2

# 构建项目
npm run build

# 启动应用
pm2 start npm --name "my-nextjs-app" -- start

# 设置开机自启
pm2 startup
pm2 save

2. PM2 配置文件

// pm2.config.js
module.exports = {
  apps: [
    {
      name: 'my-nextjs-app',
      script: 'node_modules/.bin/next',
      args: 'start',
      instances: 'max',
      exec_mode: 'cluster',
      watch: false,
      max_memory_restart: '1G',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: './logs/err.log',
      out_file: './logs/out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss',
    },
  ],
};
pm2 start pm2.config.js
pm2 status
pm2 logs my-nextjs-app
pm2 reload my-nextjs-app

五、GitHub Actions 自动化 CI/CD

手动部署效率低下且易出错。CI/CD 流水线实现:推送代码即自动完成质量检查、构建和部署

graph TB
    Push[开发者推送代码] --> GHA[GitHub Actions 触发]
    GHA --> Lint[代码检查<br/>ESLint + TypeScript]
    Lint --> Test[单元测试<br/>Vitest/Jest]
    Test --> Build[构建应用<br/>next build]
    Build --> Deploy{分支判断}
    Deploy -->|main| Prod[生产环境]
    Deploy -->|feature/*| Preview[预览环境]
    Prod --> Notify[通知团队]

1. Vercel 部署流水线

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

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

jobs:
  quality:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: TypeScript type check
        run: npx tsc --noEmit

      - name: ESLint
        run: npm run lint

      - name: Run tests
        run: npm test

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: quality
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

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

2. 自托管服务器部署

# .github/workflows/self-hosted-deploy.yml
name: Deploy to Server

on:
  push:
    branches: [main]

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

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

      - name: Install & Build
        run: |
          npm ci
          npm run build

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/my-app
            git pull origin main
            npm ci --only=production
            npm run build
            pm2 reload my-nextjs-app
            echo "✅ Deployment completed at $(date)"

3. GitHub Secrets 配置

在仓库 Settings → Secrets and variables → Actions 中添加:

Secret 名称说明获取方式
VERCEL_TOKENVercel API TokenVercel Dashboard
ORG_IDVercel 组织 IDVercel Dashboard
PROJECT_IDVercel 项目 IDVercel Dashboard
SERVER_HOST服务器 IP/域名你的服务器地址
SERVER_USERSSH 用户名服务器用户
SSH_PRIVATE_KEYSSH 私钥cat ~/.ssh/id_rsa
DATABASE_URL数据库连接串数据库提供商

六、环境变量安全管理

N1. ext.js 环境变量规则

.env                 # 所有环境共享(极少使用)
.env.development     # 仅开发环境
.env.production      # 仅生产环境
.env.local           # 本地覆盖(不提交 Git,优先级最高)

2. 客户端 vs 服务端变量

# ✅ 仅服务端可用(不会暴露给浏览器)
DATABASE_URL=postgresql://user:pass@host/db
NEXTAUTH_SECRET=super-secret-string
API_KEY=sk-xxxxxxxxx

# ⚠️ NEXT_PUBLIC_ 前缀会打包进客户端 JS(用户可见)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_ANALYTICS_ID=UA-XXXXX

⚠️ 严重安全警告: 绝对禁止将数据库密码、API 密钥等敏感信息放在 NEXT_PUBLIC_ 前缀的变量中!

3. 类型安全的环境变量(推荐)

使用 Zod 进行运行时验证:

// lib/env.ts
import { z } from 'zod';

const envSchema = z.object({
  DATABASE_URL: z.string().url(),
  NEXTAUTH_SECRET: z.string().min(32),
  NEXTAUTH_URL: z.string().url(),
  RESEND_API_KEY: z.string().startsWith('re_'),
  NEXT_PUBLIC_APP_URL: z.string().url(),
});

export const env = envSchema.parse(process.env);
// 使用时有类型提示且确保值存在
import { env } from '@/lib/env';

const response = await fetch(`${env.NEXT_PUBLIC_APP_URL}/api/data`);

七、生产环境监控

1. 健康检查接口

// app/api/health/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  const checks = {
    timestamp: new Date().toISOString(),
    status: 'healthy',
    version: process.env.npm_package_version || 'unknown',
    uptime: process.uptime(),
  };

  try {
    // await db.query('SELECT 1');
    return NextResponse.json(checks, { status: 200 });
  } catch (error) {
    return NextResponse.json(
      { 
        ...checks, 
        status: 'unhealthy', 
        error: 'Database connection failed' 
      },
      { status: 503 }
    );
  }
}

2. Sentry 错误监控

npm install @sentry/nextjs
npx @sentry/wizard@latest -i nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  enabled: process.env.NODE_ENV === 'production',
});

3. Core Web Vitals 上报

// components/WebVitals.tsx
'use client';

import { useReportWebVitals } from 'next/web-vitals';
import type { Metric } from 'web-vitals';

export function WebVitals() {
  useReportWebVitals((metric: Metric) => {
    fetch('/api/vitals', {
      method: 'POST',
      body: JSON.stringify({
        name: metric.name,
        value: metric.value,
        id: metric.id,
        label: metric.label,
      }),
      headers: { 'Content-Type': 'application/json' },
    });
  });
  
  return null;
}

八、回滚策略

1. Vercel 一键回滚

Vercel 保留所有历史部署记录,可在控制台随时选择版本重新激活。

2. Git Tag 版本管理

# 发布时打标签
git tag -a v1.2.3 -m "Release version 1.2.3"
git push origin v1.2.3

# 回滚到上一版本
git checkout v1.2.2
npm run build
pm2 reload my-nextjs-app

3. 蓝绿部署(高可用场景)

生产流量 → Nginx → 蓝色环境(当前 v1.2.2)
                 → 绿色环境(待发布 v1.2.3)

发布流程:
1. 在绿色环境部署新版本
2. 运行自动化测试
3. 切换 Nginx 流量至绿色环境
4. 蓝色环境保留作为回滚备份

九、部署前检查清单

每次生产部署前逐项确认:

  • 所有单元测试通过
  • TypeScript 类型检查通过
  • ESLint 无错误
  • 生产环境变量已配置
  • 数据库迁移已执行(如有)
  • .env.local 未提交到 Git
  • 敏感 API Key 未出现在代码中
  • next build 本地构建成功
  • 健康检查接口可访问(/api/health)
  • CDN 缓存策略已设置
  • Sentry 错误监控已配置
  • 回滚方案已确认
  • 团队成员已通知

十、方案对比总结

方案适用场景复杂度月成本维护成本
Vercel个人项目、初创公司、快速上线$0-20极低
Docker + 云服务器企业级应用、合规要求⭐⭐⭐$10-100中等
Node.js 自托管已有服务器、运维团队⭐⭐固定成本较高

无论选择哪种方案,CI/CD 流水线都是必须建立的——手动部署是事故的温床。从一开始就将质量检查和自动化部署内化到开发流程中,会让项目更健康、更稳定。