nextjs-deployment

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js Deployment

Next.js 部署

Deploy Next.js applications to production with Docker, CI/CD pipelines, and comprehensive monitoring.
使用 Docker、CI/CD 流水线和全面监控将 Next.js 应用部署到生产环境。

Overview

概述

This skill provides patterns for:
  • Docker configuration with multi-stage builds
  • GitHub Actions CI/CD pipelines
  • Environment variables management (build-time and runtime)
  • Preview deployments
  • Monitoring with OpenTelemetry
  • Logging and health checks
  • Production optimization
本方案提供以下场景的实现模式:
  • 多阶段构建的Docker配置
  • GitHub Actions CI/CD流水线
  • 环境变量管理(构建时与运行时)
  • 预览部署
  • 基于OpenTelemetry的监控
  • 日志与健康检查
  • 生产环境优化

When to Use

适用场景

Activate when user requests involve:
  • "Deploy Next.js", "Dockerize Next.js", "containerize"
  • "GitHub Actions", "CI/CD pipeline", "automated deployment"
  • "Environment variables", "runtime config", "NEXT_PUBLIC"
  • "Preview deployment", "staging environment"
  • "Monitoring", "OpenTelemetry", "tracing", "logging"
  • "Health checks", "readiness", "liveness"
  • "Production build", "standalone output"
  • "Server Actions encryption key", "NEXT_SERVER_ACTIONS_ENCRYPTION_KEY"
当用户需求涉及以下内容时启用:
  • "部署Next.js"、"Docker化Next.js"、"容器化"
  • "GitHub Actions"、"CI/CD流水线"、"自动化部署"
  • "环境变量"、"运行时配置"、"NEXT_PUBLIC"
  • "预览部署"、"预发布环境"
  • "监控"、"OpenTelemetry"、"链路追踪"、"日志"
  • "健康检查"、"就绪检查"、"存活检查"
  • "生产构建"、"独立输出"
  • "Server Actions加密密钥"、"NEXT_SERVER_ACTIONS_ENCRYPTION_KEY"

Quick Reference

快速参考

Output Modes

输出模式

ModeUse CaseCommand
standalone
Docker/container deployment
output: 'standalone'
export
Static site (no server)
output: 'export'
(default)Node.js server deployment
next start
模式适用场景命令
standalone
Docker/容器部署
output: 'standalone'
export
静态站点(无服务器)
output: 'export'
(默认)Node.js服务器部署
next start

Environment Variable Types

环境变量类型

PrefixAvailabilityUse Case
NEXT_PUBLIC_
Build-time + BrowserPublic API keys, feature flags
(no prefix)Server-onlyDatabase URLs, secrets
RuntimeServer-onlyDifferent values per environment
前缀可用范围适用场景
NEXT_PUBLIC_
构建时 + 浏览器端公开API密钥、功能开关
(无前缀)仅服务器端数据库URL、密钥
运行时仅服务器端不同环境使用不同值

Key Files

关键文件

FilePurpose
Dockerfile
Multi-stage container build
.github/workflows/deploy.yml
CI/CD pipeline
next.config.ts
Build configuration
instrumentation.ts
OpenTelemetry setup
src/app/api/health/route.ts
Health check endpoint
文件用途
Dockerfile
多阶段容器构建
.github/workflows/deploy.yml
CI/CD流水线
next.config.ts
构建配置
instrumentation.ts
OpenTelemetry设置
src/app/api/health/route.ts
健康检查端点

Instructions

操作指南

Configure Standalone Output

配置独立输出

Enable standalone output for optimized Docker deployments:
typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  generateBuildId: async () => {
    // Use git hash for consistent builds across servers
    return process.env.GIT_HASH || process.env.GITHUB_SHA || 'build'
  },
}

export default nextConfig
启用独立输出以优化Docker部署:
typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  generateBuildId: async () => {
    // 使用git哈希确保跨服务器构建一致性
    return process.env.GIT_HASH || process.env.GITHUB_SHA || 'build'
  },
}

export default nextConfig

Create Multi-Stage Dockerfile

创建多阶段Dockerfile

Build optimized Docker image with minimal footprint:
dockerfile
undefined
构建轻量级的优化Docker镜像:
dockerfile
undefined

syntax=docker/dockerfile:1

syntax=docker/dockerfile:1

FROM node:20-alpine AS base
FROM node:20-alpine AS base

Install dependencies only when needed

仅在需要时安装依赖

FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app
FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app

Install dependencies

安装依赖

COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./ RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile;
elif [ -f yarn.lock ]; then yarn --frozen-lockfile;
elif [ -f package-lock.json ]; then npm ci;
else echo "Lockfile not found." && exit 1;
fi
COPY package.json package-lock.json* pnpm-lock.yaml* yarn.lock* ./ RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile;
elif [ -f yarn.lock ]; then yarn --frozen-lockfile;
elif [ -f package-lock.json ]; then npm ci;
else echo "Lockfile not found." && exit 1;
fi

Rebuild the source code only when needed

仅在源码变更时重新构建

FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . .
FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . .

Set build-time environment variables

设置构建时环境变量

ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production

Generate build ID from git (set during build)

从git生成构建ID(构建时设置)

ARG GIT_HASH ENV GIT_HASH=${GIT_HASH}
ARG GIT_HASH ENV GIT_HASH=${GIT_HASH}

Server Actions encryption key (CRITICAL for multi-server deployments)

Server Actions加密密钥(多服务器部署必备)

ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}
RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build;
elif [ -f yarn.lock ]; then yarn build;
elif [ -f package-lock.json ]; then npm run build;
else npm run build;
fi
ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}
RUN
if [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run build;
elif [ -f yarn.lock ]; then yarn build;
elif [ -f package-lock.json ]; then npm run build;
else npm run build;
fi

Production image, copy all the files and run next

生产镜像,复制所有文件并运行next

FROM base AS runner WORKDIR /app
ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0"
RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs
FROM base AS runner WORKDIR /app
ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0"
RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs

Copy standalone output

复制独立输出文件

COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

Copy public files if they exist

复制public文件(如果存在)

COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
USER nextjs
EXPOSE 3000

Health check

健康检查

HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["node", "server.js"]
undefined
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["node", "server.js"]
undefined

Set Up GitHub Actions CI/CD

搭建GitHub Actions CI/CD

Create automated build and deployment pipeline:
yaml
undefined
创建自动化构建与部署流水线:
yaml
undefined

.github/workflows/deploy.yml

.github/workflows/deploy.yml

name: Build and Deploy
on: push: branches: [main, develop] pull_request: branches: [main]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write
steps:
  - name: Checkout
    uses: actions/checkout@v4

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

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

  - name: Extract metadata
    id: meta
    uses: docker/metadata-action@v5
    with:
      images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
      tags: |
        type=ref,event=branch
        type=ref,event=pr
        type=sha,prefix={{branch}}-
        type=raw,value=latest,enable={{is_default_branch}}

  - name: Generate Server Actions Key
    id: generate-key
    run: |
      KEY=$(openssl rand -base64 32)
      echo "key=$KEY" >> $GITHUB_OUTPUT

  - name: Build and push Docker image
    uses: docker/build-push-action@v5
    with:
      context: .
      push: true
      tags: ${{ steps.meta.outputs.tags }}
      labels: ${{ steps.meta.outputs.labels }}
      cache-from: type=gha
      cache-to: type=gha,mode=max
      build-args: |
        GIT_HASH=${{ github.sha }}
        NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.generate-key.outputs.key }}
deploy-staging: needs: build if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com
steps:
  - name: Deploy to staging
    run: |
      echo "Deploying to staging..."
      # Add your deployment commands here
      # e.g., kubectl, helm, or platform-specific CLI
deploy-production: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: name: production url: https://example.com
steps:
  - name: Deploy to production
    run: |
      echo "Deploying to production..."
      # Add your deployment commands here
undefined
name: Build and Deploy
on: push: branches: [main, develop] pull_request: branches: [main]
env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}
jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write id-token: write
steps:
  - name: Checkout
    uses: actions/checkout@v4

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

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

  - name: Extract metadata
    id: meta
    uses: docker/metadata-action@v5
    with:
      images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
      tags: |
        type=ref,event=branch
        type=ref,event=pr
        type=sha,prefix={{branch}}-
        type=raw,value=latest,enable={{is_default_branch}}

  - name: Generate Server Actions Key
    id: generate-key
    run: |
      KEY=$(openssl rand -base64 32)
      echo "key=$KEY" >> $GITHUB_OUTPUT

  - name: Build and push Docker image
    uses: docker/build-push-action@v5
    with:
      context: .
      push: true
      tags: ${{ steps.meta.outputs.tags }}
      labels: ${{ steps.meta.outputs.labels }}
      cache-from: type=gha
      cache-to: type=gha,mode=max
      build-args: |
        GIT_HASH=${{ github.sha }}
        NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.generate-key.outputs.key }}
deploy-staging: needs: build if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: name: staging url: https://staging.example.com
steps:
  - name: Deploy to staging
    run: |
      echo "Deploying to staging..."
      # 添加你的部署命令
      # 例如:kubectl、helm或平台专属CLI
deploy-production: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: name: production url: https://example.com
steps:
  - name: Deploy to production
    run: |
      echo "Deploying to production..."
      # 添加你的部署命令
undefined

Manage Environment Variables

环境变量管理

Build-Time Variables (next.config.ts)

构建时变量(next.config.ts)

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

const nextConfig: NextConfig = {
  env: {
    // These are inlined at build time
    APP_VERSION: process.env.npm_package_version || '1.0.0',
    BUILD_DATE: new Date().toISOString(),
  },
  // Public runtime config (available on server and client)
  publicRuntimeConfig: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    featureFlags: {
      newDashboard: process.env.NEXT_PUBLIC_FF_NEW_DASHBOARD === 'true',
    },
  },
}

export default nextConfig
typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  env: {
    // 这些变量在构建时被内联
    APP_VERSION: process.env.npm_package_version || '1.0.0',
    BUILD_DATE: new Date().toISOString(),
  },
  // 公开运行时配置(服务器端与客户端均可用)
  publicRuntimeConfig: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    featureFlags: {
      newDashboard: process.env.NEXT_PUBLIC_FF_NEW_DASHBOARD === 'true',
    },
  },
}

export default nextConfig

Runtime Environment Variables

运行时环境变量

For runtime variables with Docker, use a single image across environments:
typescript
// src/lib/env.ts
export function getEnv() {
  return {
    // Server-only (read at request time)
    databaseUrl: process.env.DATABASE_URL!,
    apiKey: process.env.API_KEY!,

    // Public (must be prefixed with NEXT_PUBLIC_ at build time)
    publicApiUrl: process.env.NEXT_PUBLIC_API_URL!,
  }
}

// Validate required environment variables
export function validateEnv() {
  const required = ['DATABASE_URL', 'API_KEY', 'NEXT_PUBLIC_API_URL']
  const missing = required.filter((key) => !process.env[key])

  if (missing.length > 0) {
    throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
  }
}
对于Docker环境的运行时变量,可在多环境中使用同一镜像:
typescript
// src/lib/env.ts
export function getEnv() {
  return {
    // 仅服务器端(请求时读取)
    databaseUrl: process.env.DATABASE_URL!,
    apiKey: process.env.API_KEY!,

    // 公开变量(构建时必须以NEXT_PUBLIC_为前缀)
    publicApiUrl: process.env.NEXT_PUBLIC_API_URL!,
  }
}

// 验证必填环境变量
export function validateEnv() {
  const required = ['DATABASE_URL', 'API_KEY', 'NEXT_PUBLIC_API_URL']
  const missing = required.filter((key) => !process.env[key])

  if (missing.length > 0) {
    throw new Error(`缺少必填环境变量:${missing.join(', ')}`)
  }
}

Environment Variable Files

环境变量文件

bash
undefined
bash
undefined

.env.local (development - never commit)

.env.local(开发环境 - 切勿提交)

DATABASE_URL=postgresql://localhost:5432/mydb API_KEY=dev-key NEXT_PUBLIC_API_URL=http://localhost:3000/api
DATABASE_URL=postgresql://localhost:5432/mydb API_KEY=dev-key NEXT_PUBLIC_API_URL=http://localhost:3000/api

.env.production (production defaults)

.env.production(生产环境默认值)

NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_API_URL=https://api.example.com

.env.example (template for developers)

.env.example(开发者模板)

DATABASE_URL= API_KEY= NEXT_PUBLIC_API_URL=
undefined
DATABASE_URL= API_KEY= NEXT_PUBLIC_API_URL=
undefined

Implement Health Checks

实现健康检查

Create a health check endpoint for load balancers and orchestrators:
typescript
// src/app/api/health/route.ts
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET() {
  const checks = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version || 'unknown',
    buildId: process.env.GIT_HASH || 'unknown',
    uptime: process.uptime(),
    checks: {
      memory: checkMemory(),
      // Add database, cache, etc. checks here
    },
  }

  const isHealthy = Object.values(checks.checks).every((check) => check.status === 'ok')

  return NextResponse.json(checks, {
    status: isHealthy ? 200 : 503
  })
}

function checkMemory() {
  const used = process.memoryUsage()
  const threshold = 1024 * 1024 * 1024 // 1GB

  return {
    status: used.heapUsed < threshold ? 'ok' : 'warning',
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
  }
}
为负载均衡器与编排工具创建健康检查端点:
typescript
// src/app/api/health/route.ts
import { NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET() {
  const checks = {
    status: 'healthy',
    timestamp: new Date().toISOString(),
    version: process.env.npm_package_version || 'unknown',
    buildId: process.env.GIT_HASH || 'unknown',
    uptime: process.uptime(),
    checks: {
      memory: checkMemory(),
      // 可在此添加数据库、缓存等检查
    },
  }

  const isHealthy = Object.values(checks.checks).every((check) => check.status === 'ok')

  return NextResponse.json(checks, {
    status: isHealthy ? 200 : 503
  })
}

function checkMemory() {
  const used = process.memoryUsage()
  const threshold = 1024 * 1024 * 1024 // 1GB

  return {
    status: used.heapUsed < threshold ? 'ok' : 'warning',
    heapUsed: `${Math.round(used.heapUsed / 1024 / 1024)}MB`,
    heapTotal: `${Math.round(used.heapTotal / 1024 / 1024)}MB`,
  }
}

Set Up OpenTelemetry Monitoring

配置OpenTelemetry监控

Add observability with OpenTelemetry:
typescript
// instrumentation.ts
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel({
    serviceName: process.env.OTEL_SERVICE_NAME || 'next-app',
    serviceVersion: process.env.npm_package_version,
  })
}
typescript
// instrumentation.node.ts
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app',
    [ATTR_SERVICE_VERSION]: process.env.npm_package_version || '1.0.0',
  }),
  spanProcessor: new SimpleSpanProcessor(
    new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
    })
  ),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
    }),
  }),
})

sdk.start()

// Graceful shutdown
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry terminated'))
    .catch((err) => console.error('OpenTelemetry termination error', err))
    .finally(() => process.exit(0))
})
typescript
// src/lib/logger.ts
interface LogEntry {
  level: string
  message: string
  timestamp: string
  requestId?: string
  [key: string]: unknown
}

export function createLogger(requestId?: string) {
  const base = {
    timestamp: new Date().toISOString(),
    ...(requestId && { requestId }),
  }

  return {
    info: (message: string, meta?: Record<string, unknown>) => {
      log({ level: 'info', message, ...base, ...meta })
    },
    warn: (message: string, meta?: Record<string, unknown>) => {
      log({ level: 'warn', message, ...base, ...meta })
    },
    error: (message: string, error?: Error, meta?: Record<string, unknown>) => {
      log({
        level: 'error',
        message,
        error: error?.message,
        stack: error?.stack,
        ...base,
        ...meta
      })
    },
  }
}

function log(entry: LogEntry) {
  // In production, send to structured logging service
  // In development, pretty print
  if (process.env.NODE_ENV === 'production') {
    console.log(JSON.stringify(entry))
  } else {
    console.log(`[${entry.level.toUpperCase()}] ${entry.message}`, entry)
  }
}
通过OpenTelemetry添加可观测性:
typescript
// instrumentation.ts
import { registerOTel } from '@vercel/otel'

export function register() {
  registerOTel({
    serviceName: process.env.OTEL_SERVICE_NAME || 'next-app',
    serviceVersion: process.env.npm_package_version,
  })
}
typescript
// instrumentation.node.ts
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
import { NodeSDK } from '@opentelemetry/sdk-node'
import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node'
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app',
    [ATTR_SERVICE_VERSION]: process.env.npm_package_version || '1.0.0',
  }),
  spanProcessor: new SimpleSpanProcessor(
    new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,
    })
  ),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: process.env.OTEL_EXPORTER_OTLP_METRICS_ENDPOINT,
    }),
  }),
})

sdk.start()

// 优雅关闭
process.on('SIGTERM', () => {
  sdk.shutdown()
    .then(() => console.log('OpenTelemetry terminated'))
    .catch((err) => console.error('OpenTelemetry termination error', err))
    .finally(() => process.exit(0))
})
typescript
// src/lib/logger.ts
interface LogEntry {
  level: string
  message: string
  timestamp: string
  requestId?: string
  [key: string]: unknown
}

export function createLogger(requestId?: string) {
  const base = {
    timestamp: new Date().toISOString(),
    ...(requestId && { requestId }),
  }

  return {
    info: (message: string, meta?: Record<string, unknown>) => {
      log({ level: 'info', message, ...base, ...meta })
    },
    warn: (message: string, meta?: Record<string, unknown>) => {
      log({ level: 'warn', message, ...base, ...meta })
    },
    error: (message: string, error?: Error, meta?: Record<string, unknown>) => {
      log({
        level: 'error',
        message,
        error: error?.message,
        stack: error?.stack,
        ...base,
        ...meta
      })
    },
  }
}

function log(entry: LogEntry) {
  // 生产环境下,发送至结构化日志服务
  // 开发环境下,美观打印
  if (process.env.NODE_ENV === 'production') {
    console.log(JSON.stringify(entry))
  } else {
    console.log(`[${entry.level.toUpperCase()}] ${entry.message}`, entry)
  }
}

Configure Preview Deployments

配置预览部署

Set up preview environments for pull requests:
yaml
undefined
为拉取请求设置预览环境:
yaml
undefined

.github/workflows/preview.yml

.github/workflows/preview.yml

name: Preview Deployment
on: pull_request: types: [opened, synchronize, closed]
jobs: deploy-preview: if: github.event.action != 'closed' runs-on: ubuntu-latest
steps:
  - name: Checkout
    uses: actions/checkout@v4

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

  - name: Install dependencies
    run: npm ci

  - name: Build
    run: npm run build
    env:
      NEXT_PUBLIC_API_URL: https://staging-api.example.com
      NEXT_PUBLIC_PREVIEW: 'true'

  - name: Deploy to Preview
    run: |
      # Example: Deploy to Vercel, Netlify, or your platform
      # npx vercel --token=${{ secrets.VERCEL_TOKEN }} --prebuilt
      echo "Deploying preview for PR #${{ github.event.number }}"
cleanup-preview: if: github.event.action == 'closed' runs-on: ubuntu-latest
steps:
  - name: Cleanup Preview
    run: |
      echo "Cleaning up preview for PR #${{ github.event.number }}"
undefined
name: Preview Deployment
on: pull_request: types: [opened, synchronize, closed]
jobs: deploy-preview: if: github.event.action != 'closed' runs-on: ubuntu-latest
steps:
  - name: Checkout
    uses: actions/checkout@v4

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

  - name: Install dependencies
    run: npm ci

  - name: Build
    run: npm run build
    env:
      NEXT_PUBLIC_API_URL: https://staging-api.example.com
      NEXT_PUBLIC_PREVIEW: 'true'

  - name: Deploy to Preview
    run: |
      # 示例:部署至Vercel、Netlify或你的平台
      # npx vercel --token=${{ secrets.VERCEL_TOKEN }} --prebuilt
      echo "为PR #${{ github.event.number }}部署预览环境"
cleanup-preview: if: github.event.action == 'closed' runs-on: ubuntu-latest
steps:
  - name: Cleanup Preview
    run: |
      echo "清理PR #${{ github.event.number }}的预览环境"
undefined

Handle Server Actions Encryption

处理Server Actions加密

CRITICAL: For multi-server deployments, set a consistent encryption key:
bash
undefined
重要提示:对于多服务器部署,需设置一致的加密密钥:
bash
undefined

Generate a key locally

本地生成密钥

openssl rand -base64 32
openssl rand -base64 32

Set in GitHub Actions (Secret)

在GitHub Actions中设置(密钥)

NEXT_SERVER_ACTIONS_ENCRYPTION_KEY

NEXT_SERVER_ACTIONS_ENCRYPTION_KEY

Or generate in workflow (see GitHub Actions example above)

或在流水线中生成(见上方GitHub Actions示例)


```dockerfile

```dockerfile

In Dockerfile - pass as build arg

在Dockerfile中作为构建参数传入

ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}

Without this key, Server Actions will fail with "Failed to find Server Action" errors in multi-server deployments.
ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}

如果没有此密钥,在多服务器部署中Server Actions会出现"Failed to find Server Action"错误。

Best Practices

最佳实践

Docker Best Practices

Docker最佳实践

  1. Use multi-stage builds to minimize final image size
  2. Enable standalone output for production deployments
  3. Set proper permissions with non-root user
  4. Include health checks for orchestration platforms
  5. Cache dependencies in CI/CD for faster builds
  1. 使用多阶段构建以最小化最终镜像体积
  2. 启用独立输出用于生产部署
  3. 设置正确权限使用非root用户
  4. 包含健康检查用于编排平台
  5. 在CI/CD中缓存依赖以加快构建速度

Security Checklist

安全检查清单

  • Never commit
    .env.local
    or secrets
  • Use
    NEXT_PUBLIC_
    prefix only for truly public values
  • Set
    NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
    for multi-server deployments
  • Disable telemetry in production with
    NEXT_TELEMETRY_DISABLED=1
  • Remove
    poweredByHeader
    in production
  • 切勿提交
    .env.local
    或密钥
  • 仅对真正公开的值使用
    NEXT_PUBLIC_
    前缀
  • 为多服务器部署设置
    NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
  • 在生产环境中通过
    NEXT_TELEMETRY_DISABLED=1
    禁用遥测
  • 在生产环境中移除
    poweredByHeader

Performance Optimization

性能优化

  • Use
    output: 'standalone'
    for minimal Docker images
  • Enable compression at the reverse proxy level
  • Configure CDN for static assets
  • Use
    next/image
    for optimized images
  • 使用
    output: 'standalone'
    构建轻量Docker镜像
  • 在反向代理层启用压缩
  • 为静态资源配置CDN
  • 使用
    next/image
    优化图片

Environment Management

环境管理

  • Use same Docker image across all environments
  • Inject runtime configuration via environment variables
  • Use different tags for different environments (
    latest
    ,
    staging
    )
  • Implement feature flags for gradual rollouts
  • 在所有环境中使用同一Docker镜像
  • 通过环境变量注入运行时配置
  • 为不同环境使用不同标签(
    latest
    staging
  • 实现功能开关以逐步发布

Examples

示例

Example 1: Complete Production Setup

示例1:完整生产环境配置

Input: Set up production deployment with Docker and GitHub Actions
Output:
typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  compress: true,
  generateBuildId: async () => process.env.GIT_HASH || 'build',
  env: {
    APP_VERSION: process.env.npm_package_version,
  },
}

export default nextConfig
dockerfile
undefined
输入:使用Docker和GitHub Actions搭建生产环境部署
输出
typescript
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  output: 'standalone',
  poweredByHeader: false,
  compress: true,
  generateBuildId: async () => process.env.GIT_HASH || 'build',
  env: {
    APP_VERSION: process.env.npm_package_version,
  },
}

export default nextConfig
dockerfile
undefined

Dockerfile

Dockerfile

FROM node:20-alpine AS base
FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci
FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production ARG GIT_HASH ENV GIT_HASH=${GIT_HASH} ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY} RUN npm run build
FROM base AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/public ./public USER nextjs EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" CMD ["node", "server.js"]

```yaml
FROM node:20-alpine AS base
FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json package-lock.json* ./ RUN npm ci
FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production ARG GIT_HASH ENV GIT_HASH=${GIT_HASH} ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY} RUN npm run build
FROM base AS runner WORKDIR /app ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/public ./public USER nextjs EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3
CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))" CMD ["node", "server.js"]

```yaml

.github/workflows/deploy.yml

.github/workflows/deploy.yml

name: Deploy on: push: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - id: key run: echo "key=$(openssl rand -base64 32)" >> $GITHUB_OUTPUT - uses: docker/build-push-action@v5 with: push: true tags: ${{ steps.meta.outputs.tags }} build-args: | GIT_HASH=${{ github.sha }} NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.key.outputs.key }}
undefined
name: Deploy on: push: branches: [main] env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - uses: actions/checkout@v4 - uses: docker/setup-buildx-action@v3 - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: meta uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - id: key run: echo "key=$(openssl rand -base64 32)" >> $GITHUB_OUTPUT - uses: docker/build-push-action@v5 with: push: true tags: ${{ steps.meta.outputs.tags }} build-args: | GIT_HASH=${{ github.sha }} NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${{ steps.key.outputs.key }}
undefined

Example 2: Runtime Environment Variables

示例2:运行时环境变量

Input: Configure different API URLs for staging and production
Output:
typescript
// src/lib/env.ts
const envSchema = {
  server: {
    DATABASE_URL: process.env.DATABASE_URL!,
    API_SECRET: process.env.API_SECRET!,
  },
  public: {
    NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!,
    NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'MyApp',
  },
}

export function getServerEnv() {
  return envSchema.server
}

export function getPublicEnv() {
  return envSchema.public
}

// Use in Server Components
import { getServerEnv } from '@/lib/env'

async function fetchData() {
  const env = getServerEnv()
  // Use env.DATABASE_URL
}

// Use in Client Components
import { getPublicEnv } from '@/lib/env'

function ApiClient() {
  const env = getPublicEnv()
  // Use env.NEXT_PUBLIC_API_URL
}
yaml
undefined
输入:为预发布和生产环境配置不同的API URL
输出
typescript
// src/lib/env.ts
const envSchema = {
  server: {
    DATABASE_URL: process.env.DATABASE_URL!,
    API_SECRET: process.env.API_SECRET!,
  },
  public: {
    NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL!,
    NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || 'MyApp',
  },
}

export function getServerEnv() {
  return envSchema.server
}

export function getPublicEnv() {
  return envSchema.public
}

// 在Server Components中使用
import { getServerEnv } from '@/lib/env'

async function fetchData() {
  const env = getServerEnv()
  // 使用env.DATABASE_URL
}

// 在Client Components中使用
import { getPublicEnv } from '@/lib/env'

function ApiClient() {
  const env = getPublicEnv()
  // 使用env.NEXT_PUBLIC_API_URL
}
yaml
undefined

docker-compose.yml for local development

本地开发用docker-compose.yml

version: '3.8' services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgresql://db:5432/myapp - NEXT_PUBLIC_API_URL=http://localhost:3000/api
undefined
version: '3.8' services: app: build: . ports: - "3000:3000" environment: - DATABASE_URL=postgresql://db:5432/myapp - NEXT_PUBLIC_API_URL=http://localhost:3000/api
undefined

Example 3: OpenTelemetry Integration

示例3:OpenTelemetry集成

Input: Add distributed tracing to Next.js application
Output:
typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node')
  }
}
typescript
// instrumentation.node.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(
    new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
    })
  ),
})

sdk.start()
typescript
// src/app/api/users/route.ts
import { trace } from '@opentelemetry/api'

export async function GET() {
  const tracer = trace.getTracer('next-app')

  return tracer.startActiveSpan('fetch-users', async (span) => {
    try {
      const users = await db.user.findMany()
      span.setAttribute('user.count', users.length)
      return NextResponse.json(users)
    } catch (error) {
      span.recordException(error as Error)
      throw error
    } finally {
      span.end()
    }
  })
}
输入:为Next.js应用添加分布式链路追踪
输出
typescript
// instrumentation.ts
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./instrumentation.node')
  }
}
typescript
// instrumentation.node.ts
import { NodeSDK } from '@opentelemetry/sdk-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { resourceFromAttributes } from '@opentelemetry/resources'
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: resourceFromAttributes({
    [ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'next-app',
  }),
  spanProcessor: new SimpleSpanProcessor(
    new OTLPTraceExporter({
      url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
    })
  ),
})

sdk.start()
typescript
// src/app/api/users/route.ts
import { trace } from '@opentelemetry/api'

export async function GET() {
  const tracer = trace.getTracer('next-app')

  return tracer.startActiveSpan('fetch-users', async (span) => {
    try {
      const users = await db.user.findMany()
      span.setAttribute('user.count', users.length)
      return NextResponse.json(users)
    } catch (error) {
      span.recordException(error as Error)
      throw error
    } finally {
      span.end()
    }
  })
}

Constraints and Warnings

限制与警告

Constraints

限制

  • Standalone output requires Node.js 18+
  • Server Actions encryption key must be consistent across all instances
  • Runtime environment variables only work with
    output: 'standalone'
  • Health checks need explicit route handler
  • OpenTelemetry requires instrumentation.ts at project root
  • 独立输出需要Node.js 18+
  • Server Actions加密密钥在所有实例中必须一致
  • 运行时环境变量仅在
    output: 'standalone'
    模式下生效
  • 健康检查需要显式的路由处理器
  • OpenTelemetry需要在项目根目录放置instrumentation.ts

Warnings

警告

  • Never use
    NEXT_PUBLIC_
    prefix for sensitive values
  • Always set
    NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
    for multi-server deployments
  • Without health checks, orchestrators may send traffic to unhealthy instances
  • Runtime env vars don't work with static export (
    output: 'export'
    )
  • Cache build artifacts in CI/CD to speed up builds
  • 切勿对敏感值使用
    NEXT_PUBLIC_
    前缀
  • 为多服务器部署务必设置
    NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
  • 没有健康检查的情况下,编排工具可能会将流量发送至不健康的实例
  • 运行时环境变量在静态导出(
    output: 'export'
    )模式下无效
  • 在CI/CD中缓存构建产物以加快构建速度

References

参考资料

Consult these files for detailed patterns:
  • references/docker-patterns.md - Advanced Docker configurations, multi-arch builds, optimization
  • references/github-actions.md - Complete CI/CD workflows, testing, security scanning
  • references/monitoring.md - OpenTelemetry, logging, alerting, dashboards
  • references/deployment-platforms.md - Platform-specific guides (Vercel, AWS, GCP, Azure)
查阅以下文件获取详细模式:
  • references/docker-patterns.md - 高级Docker配置、多架构构建、优化
  • references/github-actions.md - 完整CI/CD工作流、测试、安全扫描
  • references/monitoring.md - OpenTelemetry、日志、告警、仪表盘
  • references/deployment-platforms.md - 平台专属指南(Vercel、AWS、GCP、Azure)