Skip to content

deployment.tsx

文件信息

  • 📄 原文件:03_deployment.tsx
  • 🔤 语言:TypeScript (Next.js / React)

本文件介绍 Next.js 应用的构建与部署策略。涵盖静态导出、Node.js 服务器、Vercel 平台、Docker 容器化以及环境变量管理。

完整代码

tsx
/**
 * ============================================================
 *                    Next.js 部署
 * ============================================================
 * 本文件介绍 Next.js 应用的构建与部署策略。
 * 涵盖静态导出、Node.js 服务器、Vercel 平台、
 * Docker 容器化以及环境变量管理。
 *
 * 适用版本:Next.js 14 / 15(App Router)
 * ============================================================
 */

import type { NextConfig } from 'next';

// ============================================================
//                    1. 构建过程
// ============================================================

/**
 * 【next build 构建命令】
 * - 执行 `next build` 将应用编译为生产版本
 * - 自动进行静态分析、代码分割、Tree Shaking
 * - 预渲染所有静态页面
 *
 * 【构建输出标记】
 * ○ (Static)   — 静态 HTML
 * ● (SSG)      — 静态生成(带 generateStaticParams)
 * λ (Dynamic)  — 服务端动态渲染
 *
 * 【.next/ 目录结构】
 * .next/
 * ├── cache/         — 构建缓存
 * ├── server/        — 服务端渲染代码
 * │   └── app/       — App Router 页面
 * ├── static/        — 静态资源(JS/CSS/字体)
 * └── BUILD_ID       — 构建唯一标识
 */

// package.json 常用脚本
const packageScripts = {
    scripts: {
        'dev':     'next dev',
        'dev:turbo': 'next dev --turbo',  // Turbopack 加速
        'build':   'next build',
        'start':   'next start',          // 启动生产服务器
        'lint':    'next lint',
        'analyze': 'ANALYZE=true next build',
    },
};

// 跳过类型检查(不推荐,CI 中应急可用)
const skipCheckConfig: NextConfig = {
    typescript: { ignoreBuildErrors: true },
    eslint: { ignoreDuringBuilds: true },
};


// ============================================================
//                    2. 静态导出
// ============================================================

/**
 * 【output: 'export' 静态导出】
 * - 导出为纯静态 HTML/CSS/JS,部署到任何静态托管
 * - 不需要 Node.js 服务器
 *
 * 【限制条件 — 不支持以下功能】
 * - 服务端渲染(动态 SSR)
 * - API Routes(/api/*)
 * - 中间件(middleware.ts)
 * - 增量静态再生(ISR / revalidate)
 * - next/image 优化 API(需自定义 loader)
 */

const staticExportConfig: NextConfig = {
    output: 'export',
    images: { unoptimized: true },   // 禁用图片优化 API
    trailingSlash: true,             // /about → /about/index.html
};

// 自定义图片 loader(用于 CDN)
function customImageLoader({ src, width, quality }: {
    src: string; width: number; quality?: number;
}) {
    return `https://cdn.example.com${src}?w=${width}&q=${quality || 75}`;
}

// 静态导出的动态路由 — 必须提供 generateStaticParams
export async function generateStaticParams() {
    const posts = await fetch('https://api.example.com/posts').then(r => r.json());
    return posts.map((post: any) => ({ slug: post.slug }));
}

// 构建:next build → 输出 out/ 目录
// 部署:将 out/ 上传到 Nginx、GitHub Pages 等


// ============================================================
//                    3. Node.js 服务器
// ============================================================

/**
 * 【next start — Node.js 服务器部署】
 * - 支持全部 Next.js 功能(SSR、API、ISR、中间件)
 * - 需要 Node.js 运行环境
 *
 * 【standalone 输出模式】
 * - 自动收集依赖,生成独立可运行的目录
 * - 不需要完整 node_modules/,大幅减小部署体积
 * - 特别适合 Docker 容器化部署
 */

const standaloneConfig: NextConfig = {
    output: 'standalone',
};

// standalone 目录结构:
// .next/standalone/
// ├── node_modules/   — 仅必要依赖
// ├── server.js       — 入口文件
// └── .next/server/
//
// 启动:node .next/standalone/server.js
// 注意:需手动复制 public/ 和 .next/static/
// cp -r public .next/standalone/public
// cp -r .next/static .next/standalone/.next/static

// PM2 进程管理
const pm2Config = {
    apps: [{
        name: 'nextjs-app',
        script: '.next/standalone/server.js',
        instances: 'max',            // 所有 CPU 核心
        exec_mode: 'cluster',        // 集群模式
        env: {
            PORT: 3000,
            NODE_ENV: 'production',
        },
        max_memory_restart: '1G',
    }],
};
// 启动:pm2 start ecosystem.config.js
// 日志:pm2 logs nextjs-app


// ============================================================
//                    4. Vercel 部署
// ============================================================

/**
 * 【Vercel — Next.js 官方托管平台】
 * - 零配置部署:推送代码自动构建
 * - 自动 CDN 分发、HTTPS、域名管理
 * - Preview Deployment — 每个 PR 独立预览环境
 * - 内置 Edge Functions、Analytics
 *
 * 【部署流程】
 * 1. 推送代码到 GitHub
 * 2. Vercel 导入项目
 * 3. 自动检测 Next.js 并配置构建
 * 4. 每次推送自动部署
 */

// vercel.json(可选配置)
const vercelConfig = {
    redirects: [
        { source: '/old-path', destination: '/new-path', permanent: true },
    ],
    headers: [
        {
            source: '/api/(.*)',
            headers: [
                { key: 'Access-Control-Allow-Origin', value: '*' },
                { key: 'Cache-Control', value: 'no-store' },
            ],
        },
        {
            source: '/(.*)',
            headers: [
                { key: 'X-Frame-Options', value: 'DENY' },
            ],
        },
    ],
    functions: {
        'app/api/**': { maxDuration: 30, memory: 1024 },
    },
    regions: ['hkg1', 'sin1'],  // 香港、新加坡
};

// Vercel CLI 命令
// npm i -g vercel
// vercel              — 部署预览环境
// vercel --prod       — 部署生产环境
// vercel env pull     — 拉取环境变量到 .env.local


// ============================================================
//                    5. Docker 部署
// ============================================================

/**
 * 【Docker 多阶段构建】
 * - 搭配 output: 'standalone' 效果最佳
 * - 三个阶段:安装依赖 → 构建 → 运行
 * - 最终镜像通常 100-200MB
 */

// --- 推荐的 Dockerfile ---
const dockerfile = `
# 阶段 1:安装依赖
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci

# 阶段 2:构建应用
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN npm run build

# 阶段 3:生产运行(最小化镜像)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

# 安全:创建非 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"]
`;

// .dockerignore
const dockerignore = `
node_modules
.next
.git
*.md
.env*.local
`;

// docker-compose.yml
const dockerCompose = `
version: '3.8'
services:
  nextjs:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - NEXT_PUBLIC_API_URL=https://api.example.com
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
`;

// Docker 常用命令:
// docker build -t my-nextjs-app .
// docker run -p 3000:3000 my-nextjs-app
// docker compose up -d


// ============================================================
//                    6. 环境变量
// ============================================================

/**
 * 【环境变量体系】
 *
 * 加载优先级(高 → 低):
 * 1. process.env(系统/平台注入)
 * 2. .env.$(NODE_ENV).local
 * 3. .env.local(本地覆盖,git 忽略)
 * 4. .env.$(NODE_ENV)
 * 5. .env(默认值)
 *
 * 【NEXT_PUBLIC_ 前缀】
 * - 带前缀:内联到客户端 JS,浏览器可见
 * - 不带前缀:仅服务端可用(Server Components / API)
 * - 敏感信息绝不使用 NEXT_PUBLIC_!
 *
 * 【构建时 vs 运行时】
 * - NEXT_PUBLIC_ 在构建时替换为字面量
 * - 服务端变量在运行时从 process.env 读取
 */

// .env 文件示例
// DATABASE_URL=postgresql://localhost:5432/mydb     ← 仅服务端
// API_SECRET_KEY=sk_test_xxxxx                      ← 仅服务端
// NEXT_PUBLIC_APP_NAME=我的应用                      ← 客户端可见
// NEXT_PUBLIC_API_URL=https://api.example.com       ← 客户端可见

// Server Component(可访问所有变量)
async function ServerPage() {
    const dbUrl = process.env.DATABASE_URL;              // ✅ 仅服务端
    const secret = process.env.API_SECRET_KEY;           // ✅ 仅服务端
    const appName = process.env.NEXT_PUBLIC_APP_NAME;    // ✅ 两端均可

    const data = await fetch(process.env.NEXT_PUBLIC_API_URL + '/data', {
        headers: { 'Authorization': `Bearer ${secret}` },
    });
    return <div>{appName}</div>;
}

// Client Component(仅公开变量)
// 'use client'
function ClientComponent() {
    const apiUrl = process.env.NEXT_PUBLIC_API_URL;      // ✅ 可用
    // const secret = process.env.API_SECRET_KEY;         // ❌ undefined
    return <p>API: {apiUrl}</p>;
}

// 类型安全的环境变量校验(使用 zod)
// import { z } from 'zod';
// const envSchema = z.object({
//     DATABASE_URL: z.string().url(),
//     API_SECRET_KEY: z.string().min(10),
//     NEXT_PUBLIC_API_URL: z.string().url(),
//     NEXT_PUBLIC_APP_NAME: z.string(),
// });
// export const env = envSchema.parse(process.env);


// ============================================================
//                    7. 最佳实践
// ============================================================

/**
 * 【部署最佳实践】
 *
 * ✅ 推荐做法:
 * 1. 使用 output: 'standalone' 减小部署体积
 * 2. Docker 多阶段构建 + 非 root 用户运行
 * 3. 敏感信息通过平台环境变量注入,不提交到 Git
 * 4. NEXT_PUBLIC_ 变量仅用于非敏感的公开信息
 * 5. 使用 .env.local 管理本地开发配置
 * 6. 生产构建前执行类型检查和 Lint
 * 7. 使用 PM2 或 systemd 管理 Node.js 进程
 * 8. 配置健康检查端点 /api/health
 *
 * ❌ 避免做法:
 * 1. 将 .env.local 或含密钥的 .env 提交到 Git
 * 2. 在 NEXT_PUBLIC_ 中存放 API 密钥 → 浏览器可见
 * 3. 生产环境使用 next dev → 无性能优化
 * 4. Docker 中用 root 用户运行 → 安全风险
 * 5. 忽略构建时的类型错误 → 隐藏 bug
 * 6. 不配置 .dockerignore → 镜像体积膨胀
 * 7. 硬编码环境配置 → 难以多环境切换
 */

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布