nextjs-deployment
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseNext.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
输出模式
| Mode | Use Case | Command |
|---|---|---|
| Docker/container deployment | |
| Static site (no server) | |
| (default) | Node.js server deployment | |
| 模式 | 适用场景 | 命令 |
|---|---|---|
| Docker/容器部署 | |
| 静态站点(无服务器) | |
| (默认) | Node.js服务器部署 | |
Environment Variable Types
环境变量类型
| Prefix | Availability | Use Case |
|---|---|---|
| Build-time + Browser | Public API keys, feature flags |
| (no prefix) | Server-only | Database URLs, secrets |
| Runtime | Server-only | Different values per environment |
| 前缀 | 可用范围 | 适用场景 |
|---|---|---|
| 构建时 + 浏览器端 | 公开API密钥、功能开关 |
| (无前缀) | 仅服务器端 | 数据库URL、密钥 |
| 运行时 | 仅服务器端 | 不同环境使用不同值 |
Key Files
关键文件
| File | Purpose |
|---|---|
| Multi-stage container build |
| CI/CD pipeline |
| Build configuration |
| OpenTelemetry setup |
| Health check endpoint |
| 文件 | 用途 |
|---|---|
| 多阶段容器构建 |
| CI/CD流水线 |
| 构建配置 |
| OpenTelemetry设置 |
| 健康检查端点 |
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 nextConfigCreate Multi-Stage Dockerfile
创建多阶段Dockerfile
Build optimized Docker image with minimal footprint:
dockerfile
undefined构建轻量级的优化Docker镜像:
dockerfile
undefinedsyntax=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
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
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
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
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 -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["node", "server.js"]
undefinedHEALTHCHECK --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 -e "require('http').get('http://localhost:3000/api/health', (r) => r.statusCode === 200 ? process.exit(0) : process.exit(1))"
CMD ["node", "server.js"]
undefinedSet 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 CLIdeploy-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 hereundefinedname: 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或平台专属CLIdeploy-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..."
# 添加你的部署命令undefinedManage 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 nextConfigtypescript
// 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 nextConfigRuntime 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
undefinedbash
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=
undefinedDATABASE_URL=
API_KEY=
NEXT_PUBLIC_API_URL=
undefinedImplement 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 }}"undefinedname: 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 }}的预览环境"undefinedHandle Server Actions Encryption
处理Server Actions加密
CRITICAL: For multi-server deployments, set a consistent encryption key:
bash
undefined重要提示:对于多服务器部署,需设置一致的加密密钥:
bash
undefinedGenerate 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
```dockerfileIn 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最佳实践
- Use multi-stage builds to minimize final image size
- Enable standalone output for production deployments
- Set proper permissions with non-root user
- Include health checks for orchestration platforms
- Cache dependencies in CI/CD for faster builds
- 使用多阶段构建以最小化最终镜像体积
- 启用独立输出用于生产部署
- 设置正确权限使用非root用户
- 包含健康检查用于编排平台
- 在CI/CD中缓存依赖以加快构建速度
Security Checklist
安全检查清单
- Never commit or secrets
.env.local - Use prefix only for truly public values
NEXT_PUBLIC_ - Set for multi-server deployments
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - Disable telemetry in production with
NEXT_TELEMETRY_DISABLED=1 - Remove in production
poweredByHeader
- 切勿提交或密钥
.env.local - 仅对真正公开的值使用前缀
NEXT_PUBLIC_ - 为多服务器部署设置
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - 在生产环境中通过禁用遥测
NEXT_TELEMETRY_DISABLED=1 - 在生产环境中移除
poweredByHeader
Performance Optimization
性能优化
- Use for minimal Docker images
output: 'standalone' - Enable compression at the reverse proxy level
- Configure CDN for static assets
- Use for optimized images
next/image
- 使用构建轻量Docker镜像
output: 'standalone' - 在反向代理层启用压缩
- 为静态资源配置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 nextConfigdockerfile
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 nextConfigdockerfile
undefinedDockerfile
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"]
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"]
```yamlFROM 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"]
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 }}
undefinedname: 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 }}
undefinedExample 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
undefineddocker-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
undefinedversion: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://db:5432/myapp
- NEXT_PUBLIC_API_URL=http://localhost:3000/api
undefinedExample 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 prefix for sensitive values
NEXT_PUBLIC_ - Always set for multi-server deployments
NEXT_SERVER_ACTIONS_ENCRYPTION_KEY - 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)