部署指南

Next.js 与 TanStack Start 应用的部署策略与最佳实践

自托管部署对比

在企业环境中,自托管(K8s + Helm)是最常见的部署方式。两种框架在自托管场景下有显著差异:

部署模式对比

维度Next.jsTanStack Start (Nitro)
架构Hybrid Standalone ServerVite + Nitro(可分离部署)
Node Server✅ 支持✅ 支持
Static + Server 分离❌ 不支持✅ 支持
纯静态导出⚠️ 支持但功能受限✅ SPA 模式
静态资源性能🔴 所有请求经过 Node🟢 可直接 CDN
动态路由✅ 原生支持✅ 原生支持
ISR✅ 支持❌ 不支持
部署复杂度🟢 简单(单容器)🟡 中等(可选分离)

Next.js 部署局限性

Standalone 模式性能问题

Next.js output: "standalone" 生成的是一个 Hybrid Server,所有请求(包括静态资源)都经过 Node.js 处理:

  • 📉 静态页面多时性能下降明显
  • 🔄 无法将静态资源直接部署到 CDN
  • 💾 内存占用较高

Static Export 不支持的特性官方文档):

不支持的特性说明
动态路由(无 generateStaticParams必须预先生成所有路径
dynamicParams = true无法运行时生成页面
Route Handlers(非 GET)POST/PUT/DELETE 等不可用
Cookies / Headers服务端 API 不可用
Rewrites / Redirects需在 CDN/Nginx 层配置
Middleware完全不可用
ISR增量静态再生成不可用
Image Optimization需自定义 loader
Draft Mode预览模式不可用

TanStack Start (Nitro) 部署优势

TanStack Start 基于 Nitro,支持灵活的部署策略:

部署模式说明适用场景
Node Server完整服务端渲染需要 SSR 的应用
Static + Node 分离静态资源 CDN + API 到 Node🌟 推荐:高性能 + 完整功能
SPA 模式纯客户端渲染后台管理系统
Hybrid Presets按平台优化Cloudflare、Vercel 等

分离部署架构

┌─────────────┐     ┌─────────────┐
│   CDN/OSS   │     │  K8s Pod    │
│  (静态资源)  │     │  (Node API) │
│             │     │             │
│  .output/   │     │  .output/   │
│   public/   │     │   server/   │
└─────────────┘     └─────────────┘
       ↑                   ↑
       └───────┬───────────┘

         ┌─────┴─────┐
         │  Ingress  │
         │  /api/*   │ → Node
         │  /*       │ → CDN
         └───────────┘

Docker 容器化(自托管)

Next.js Dockerfile

必须配置:在 next.config.js 中开启 output: "standalone"

Dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine AS base

# 安装 Bun
RUN apk add --no-cache bash curl
ENV BUN_INSTALL=/usr/local/bun
RUN curl -fsSL https://bun.com/install | bash
ENV PATH="${BUN_INSTALL}/bin:${PATH}"

# 依赖安装
FROM base AS deps
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun i --frozen-lockfile

# 构建
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN bun run build

# 运行
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

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 HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

TanStack Start Dockerfile

方案 A:完整 Node Server(简单)

Dockerfile
# syntax=docker/dockerfile:1

FROM node:24-alpine AS base

# 安装 Bun
RUN apk add --no-cache bash curl
ENV BUN_INSTALL=/usr/local/bun
RUN curl -fsSL https://bun.com/install | bash
ENV PATH="${BUN_INSTALL}/bin:${PATH}"

# 依赖安装
FROM base AS deps
WORKDIR /app
COPY package.json bun.lock* ./
RUN bun i --frozen-lockfile

# 构建
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN bun run build

# 运行
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser

COPY --from=builder --chown=appuser:nodejs /app/.output ./

USER appuser
EXPOSE 3000
ENV PORT=3000 HOST="0.0.0.0"
CMD ["node", ".output/server/index.mjs"]

方案 B:静态资源分离(推荐)

Dockerfile.server
# 仅构建 Server 部分
FROM node:24-alpine AS base
# ... 同上 ...

FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production

# 只复制 server 目录
COPY --from=builder --chown=appuser:nodejs /app/.output/server ./

USER appuser
EXPOSE 3000
CMD ["node", "index.mjs"]
构建后目录结构
.output/
├── public/          # → 上传到 CDN/OSS
│   ├── _build/
│   └── assets/
└── server/          # → 打包到 Docker 镜像
    └── index.mjs

Helm Chart 配置示例

values.yaml
replicaCount: 3

image:
  repository: registry.example.com/my-app
  tag: latest
  pullPolicy: Always

service:
  type: ClusterIP
  port: 3000

ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

env:
  - name: NODE_ENV
    value: production
  - name: API_URL
    valueFrom:
      configMapKeyRef:
        name: app-config
        key: api-url

livenessProbe:
  httpGet:
    path: /health
    port: 3000
  initialDelaySeconds: 10

readinessProbe:
  httpGet:
    path: /health
    port: 3000
  initialDelaySeconds: 5

CI/CD Pipeline(GitLab CI 示例)

.gitlab-ci.yml
stages:
  - test
  - build
  - deploy

variables:
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

test:
  stage: test
  image: oven/bun:latest
  script:
    - bun install --frozen-lockfile
    - bun run lint
    - bun run test

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG

deploy:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - helm upgrade --install my-app ./helm
      --set image.tag=$CI_COMMIT_SHA
      --namespace production
  only:
    - main

云平台部署

环境变量管理

安全原则

类型存放位置示例
🔓 公开配置代码仓库 .env.exampleNEXT_PUBLIC_API_URL
🔒 敏感凭证K8s Secrets / VaultDATABASE_URL, API_KEY
🔐 运行时密钥HashiCorp Vault / KMS加密密钥、JWT Secret

K8s ConfigMap & Secret

configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  API_URL: "https://api.example.com"
  APP_NAME: "MyApp"
secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  DATABASE_URL: "postgresql://..."
  JWT_SECRET: "your-secret-key"

🚨 永远不要将 Secret YAML 提交到 Git 仓库!使用 Sealed Secrets 或外部密钥管理。

监控与可观测性

类型工具说明
📊 APMSentry错误追踪、性能监控
📈 指标Prometheus + GrafanaK8s 原生监控
📝 日志ELK / Loki集中式日志管理
🔍 追踪Jaeger / OpenTelemetry分布式追踪

健康检查端点

app/api/health/route.ts (Next.js)
export async function GET() {
  return Response.json({ status: "ok", timestamp: Date.now() })
}
src/routes/api/health.ts (TanStack Start)
import { createAPIFileRoute } from "@tanstack/react-start/api"

export const APIRoute = createAPIFileRoute("/api/health")({
  GET: () => Response.json({ status: "ok", timestamp: Date.now() }),
})

Checklist

  • 选择适合团队的部署架构(单容器 / 静态分离)
  • 配置 CI/CD 自动化流程(GitLab CI / GitHub Actions)
  • Helm Chart 配置完成并测试
  • K8s ConfigMap & Secret 配置正确
  • 健康检查端点 /health 就绪
  • Liveness / Readiness Probe 配置
  • 资源限制(CPU/Memory)合理设置
  • 日志采集与监控告警配置
  • 回滚策略文档化

目录