多平台部署策略与选型

Nuxt4 的部署灵活性是框架的核心竞争力之一。同一份代码,通过 Nitro 的 preset 机制,可以部署到 Node.js 服务器、Docker 容器、Serverless 平台、边缘节点甚至生成纯静态站点。部署方式的选择直接影响成本、性能、运维复杂度和扩展性。本章从 Nuxt4 的渲染模式和构建产物出发,系统对比主流部署方案的取舍。

1. 渲染模式与部署的关系

1.1 三种渲染模式

在讨论部署之前,必须先理解渲染模式——它决定了构建产物的形态:

渲染模式构建产物运行时需求适用场景
SSR(默认)Node.js 服务端代码 + 客户端 bundle需要 Node.js 服务器动态内容、SEO、个性化
SSGnuxi generate纯静态 HTML + JS/CSS仅需静态文件服务博客、文档、营销页
SPA单个 HTML + JS bundle仅需静态文件服务后台管理系统

关键认知:渲染模式决定了可选的部署平台范围。SSG/SPA 可以部署到任何静态托管(CDN、OSS、GitHub Pages),而 SSR 必须有运行时环境(Node.js / Serverless / Edge)。

1.2 Nitro Preset:一键切换部署目标

Nitro 是 Nuxt4 的服务端引擎,它的 preset 机制让同一份代码适配不同平台:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'node-server',  // 或 'vercel', 'cloudflare', 'netlify' 等
  },
})

Nitro 会根据 preset 生成对应平台的部署产物——Node.js 的生成可执行文件,Vercel 的生成 Serverless Functions,Cloudflare 的生成 Workers 脚本。你不需要修改业务代码,只需切换 preset。

2. Node.js 服务器部署

2.1 适用场景

  • 需要完整 Node.js 运行时能力(文件系统、长连接、WebSocket)
  • 自有服务器或云主机(ECS、EC2)
  • 需要精细控制进程管理、内存、CPU

2.2 构建与运行

# 构建
NITRO_PRESET=node-server nuxi build
 
# 产物结构
.output/
├── server/
   └── index.mjs Node.js 入口
├── public/ 静态资源
└── nitro.json 配置
# 启动
node .output/server/index.mjs
# 服务器监听 http://localhost:3000

2.3 PM2 进程管理

生产环境不应该用 node 直接启动——进程崩溃后无法自动重启。PM2 是 Node.js 生态最成熟的进程管理器:

// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'nuxt-app',
    script: '.output/server/index.mjs',
    instances: 'max',      // 使用所有 CPU 核心
    exec_mode: 'cluster',  // 集群模式
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      NITRO_PORT: 3000,
    },
    max_memory_restart: '1G',
    error_file: './logs/error.log',
    out_file: './logs/out.log',
  }],
}

PM2 集群模式的核心价值:

  • 多进程instances: 'max' 启动与 CPU 核心数相同的进程,充分利用多核
  • 零停机重启pm2 reload 逐个重启进程,始终有进程在处理请求
  • 自动重启:进程崩溃后自动拉起,max_memory_restart 防止内存泄漏
  • 日志管理:自动轮转日志文件

2.4 Nginx 反向代理

upstream nuxt_app {
    server 127.0.0.1:3000;
    keepalive 64;
}
 
server {
    listen 80;
    server_name example.com;
 
    location / {
        proxy_pass http://nuxt_app;
        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;
    }
 
    # 静态资源直接由 Nginx 提供(绕过 Node.js)
    location /_nuxt/ {
        alias .output/public/_nuxt/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

关键优化:静态资源由 Nginx 直接提供/_nuxt/ 路径),不经过 Node.js 进程。这减少了 Node.js 的负载,Nginx 在服务静态文件方面比 Node.js 快一个数量级。

3. Docker 容器化部署

3.1 适用场景

  • 需要环境一致性(开发/测试/生产环境完全相同)
  • Kubernetes 编排
  • 微服务架构
  • 团队有容器化运维经验

3.2 多阶段构建

# 阶段 1:依赖安装
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
 
# 阶段 2:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN corepack enable && pnpm build
 
# 阶段 3:运行时(最小镜像)
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.output ./.output
 
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
 
CMD ["node", ".output/server/index.mjs"]

多阶段构建的意义:

  • deps 阶段:单独安装依赖,利用 Docker 层缓存——依赖不变时跳过此步
  • builder 阶段:构建应用,包含完整的 devDependencies
  • runner 阶段:只复制 .output,最终镜像不包含 node_modules、源代码、构建工具

最终镜像大小:约 80-120MB(Node.js Alpine + .output),而非 1GB+(包含所有 devDependencies)。

3.3 Docker Compose

# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://db:5432/myapp
      - NUXT_SESSION_SECRET=${SESSION_SECRET}
    depends_on:
      - db
      - redis
    restart: unless-stopped
 
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_PASSWORD=${DB_PASSWORD}
 
  redis:
    image: redis:7-alpine
 
volumes:
  pgdata:

3.4 健康检查

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --spider -q http://localhost:3000/api/health || exit 1
// server/api/health.get.ts
export default defineEventHandler(() => ({
  status: 'ok',
  timestamp: Date.now(),
}))

4. Serverless 部署

4.1 适用场景

  • 流量波动大(峰谷差 10 倍以上)
  • 不想运维服务器
  • 按请求计费更经济
  • 全球分布式部署

4.2 Vercel

Nuxt 对 Vercel 有一等支持——自动检测 Nuxt 项目并选择正确的 preset:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    preset: 'vercel',  // 通常自动检测,无需手动设置
  },
})

Vercel 的部署产物:

  • Serverless Functions:每个 server route 变成一个独立的 Lambda 函数
  • Edge Functions:可选,将部分逻辑部署到边缘节点
  • Static Assets:自动上传到 CDN

优势:零配置部署、自动 HTTPS、Preview Deployments(每个 PR 一个预览环境)、全球 CDN。

局限:函数执行时间限制(Hobby 10s,Pro 60s)、不支持 WebSocket、冷启动延迟。

4.3 Netlify

nitro: {
  preset: 'netlify',
}

Netlify 与 Vercel 类似,但有一些差异:

  • 函数超时更长(Background Functions 可达 15 分钟)
  • 内置表单处理(无需后端)
  • 不支持 Edge SSR(只有 Edge Functions 用于中间件)

4.4 Cloudflare Pages

nitro: {
  preset: 'cloudflare-pages',
}

Cloudflare Pages 的独特优势:

  • 全球 300+ 边缘节点,延迟极低
  • 免费额度慷慨(无限请求数,100,000 次/天构建)
  • 与 Cloudflare 生态深度集成(D1 数据库、R2 存储、KV 缓存)

局限:Workers 运行时不是完整的 Node.js——不支持文件系统、某些 Node.js API。需要确保代码兼容 Workers 环境。

4.5 Serverless 冷启动问题

Serverless 的最大痛点是冷启动——函数长时间未被调用后,下次调用需要重新初始化运行时环境,延迟可达 1-3 秒。

缓解方案:

  • Provisioned Concurrency(Vercel):保持一定数量的热实例
  • Edge Functions:V8 Isolate 冷启动 < 5ms,但 API 受限
  • 定时保活:定期 ping 函数,保持热状态(不推荐,浪费资源)

5. 静态站点部署

5.1 SSG 生成

nuxi generate

生成纯静态 HTML 文件,可以部署到任何静态托管服务。

5.2 部署目标

平台费用特点
GitHub Pages免费适合开源项目
Cloudflare Pages免费全球 CDN,速度快
Vercel免费额度自动 Preview Deployments
Netlify免费额度表单处理、分支部署
阿里云 OSS + CDN按量付费国内访问快
AWS S3 + CloudFront按量付费全球覆盖

5.3 国内部署方案

国内用户访问海外平台(Vercel、Netlify、Cloudflare)可能较慢。国内部署推荐:

nuxi generate → 上传到 OSS → 配置 CDN 加速域名 → 备案

阿里云 OSS 配置要点:

  • 开启静态网站托管
  • 配置 index.html 为默认首页
  • 404 页面也指向 index.html(SPA 路由兼容)
  • CDN 配置缓存规则:_nuxt/ 路径长期缓存,HTML 文件短缓存

6. 混合渲染:Route Rules

6.1 同一项目不同路由不同渲染模式

Nuxt4 最强大的部署特性之一是 Route Rules——同一个项目中,不同路由可以使用不同的渲染策略:

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },              // 首页:构建时预渲染
    '/blog/**': { isr: 3600 },             // 博客:ISR,1 小时重新生成
    '/dashboard/**': { ssr: false },       // 后台:纯 SPA,不需要 SSR
    '/api/**': { cors: true, cache: false }, // API:允许 CORS,不缓存
    '/static/**': { static: true },        // 静态页:完全静态化
  },
})

6.2 ISR(Incremental Static Regeneration)

ISR 是 SSG 和 SSR 的折中——页面在首次请求时 SSR 渲染并缓存,后续请求直接返回缓存,过期后后台重新生成:

用户 A 请求 /blog/hello → SSR 渲染 → 缓存 1 小时
用户 B 请求 /blog/hello → 返回缓存(快)
1 小时后用户 C 请求 → 返回旧缓存 + 后台重新渲染
用户 D 请求 → 返回新缓存

ISR 适合内容更新频率不高但需要 SEO 的页面(博客、产品页)。

7. 部署选型决策

7.1 决策矩阵

因素Node.js + PM2Docker + K8sVercel/NetlifyCloudflare静态托管
运维复杂度
成本(低流量)高(固定成本)免费免费免费
成本(高流量)可控可控可能很贵便宜便宜
冷启动极低
Node.js 兼容完整完整完整部分不需要
全球延迟取决于服务器位置多区域部署极好CDN
SSR 支持
WebSocket有限

7.2 推荐方案

  • 个人项目/博客:SSG + Cloudflare Pages(免费、快速)
  • 中小型 SaaS:Vercel(零运维、Preview Deployments 提高开发效率)
  • 大流量网站:Cloudflare Pages + Workers(全球边缘、成本可控)
  • 企业内部系统:Docker + K8s(环境一致性、内网部署)
  • 需要完整 Node.js:PM2 + Nginx(WebSocket、文件处理、长任务)

本章小结

  • 渲染模式决定部署选项:SSR 需要运行时环境,SSG/SPA 只需静态托管
  • Nitro preset:一行配置切换部署目标,业务代码无需修改
  • Node.js + PM2:集群模式多核利用 + 零停机重启 + Nginx 反向代理静态资源
  • Docker:多阶段构建保持镜像精简(~100MB),Docker Compose 管理完整栈
  • Serverless:Vercel / Netlify / Cloudflare,零运维但有冷启动和 API 限制
  • Route Rules:同一项目不同路由用不同渲染策略(prerender / ISR / SPA / SSR)
  • 选型原则:按流量规模、运维能力、Node.js 需求和成本预算综合决策