nextjs-deployment by giuseppe-trisciuoglio/developer-kit
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-deployment使用 Docker、CI/CD 流水线和全面的监控,将 Next.js 应用程序部署到生产环境。
此技能提供以下模式:
当用户请求涉及以下内容时激活:
| 模式 | 使用场景 | 命令 |
|---|---|---|
standalone | Docker/容器部署 | output: 'standalone' |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
export | 静态站点(无服务器) | output: 'export' |
| (默认) | Node.js 服务器部署 | next start |
| 前缀 | 可用性 | 使用场景 |
|---|---|---|
NEXT_PUBLIC_ | 构建时 + 浏览器 | 公共 API 密钥、功能开关 |
| (无前缀) | 仅服务器 | 数据库 URL、密钥 |
| 运行时 | 仅服务器 | 每个环境不同的值 |
| 文件 | 用途 |
|---|---|
Dockerfile | 多阶段容器构建 |
.github/workflows/deploy.yml | CI/CD 流水线 |
next.config.ts | 构建配置 |
instrumentation.ts | OpenTelemetry 设置 |
src/app/api/health/route.ts | 健康检查端点 |
为优化的 Docker 部署启用独立输出:
// 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
构建占用空间最小的优化 Docker 镜像:
# syntax=docker/dockerfile:1
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* 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
# 仅在需要时重新构建源代码
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 设置构建时环境变量
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
# 从 git 生成构建 ID(在构建期间设置)
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 \
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
# 生产镜像,复制所有文件并运行 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
# 复制独立输出
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"]
创建自动化构建和部署流水线:
# .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..."
# 在此处添加您的部署命令
# 例如: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..."
# 在此处添加您的部署命令
// 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
对于使用 Docker 的运行时变量,在所有环境中使用单个镜像:
// 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 required environment variables: ${missing.join(', ')}`)
}
}
# .env.local (开发环境 - 切勿提交)
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=dev-key
NEXT_PUBLIC_API_URL=http://localhost:3000/api
# .env.production (生产环境默认值)
NEXT_PUBLIC_API_URL=https://api.example.com
# .env.example (开发者模板)
DATABASE_URL=
API_KEY=
NEXT_PUBLIC_API_URL=
为负载均衡器和编排器创建健康检查端点:
// 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`,
}
}
使用 OpenTelemetry 添加可观测性:
// 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,
})
}
// 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))
})
// 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)
}
}
为拉取请求设置预览环境:
# .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: |
# 示例:部署到 Vercel、Netlify 或您的平台
# 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 }}"
至关重要:对于多服务器部署,设置一致的加密密钥:
# 在本地生成密钥
openssl rand -base64 32
# 在 GitHub Actions 中设置(作为 Secret)
# NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
# 或者在工作流中生成(参见上面的 GitHub Actions 示例)
# 在 Dockerfile 中 - 作为构建参数传递
ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=${NEXT_SERVER_ACTIONS_ENCRYPTION_KEY}
如果没有此密钥,在多服务器部署中,服务器操作将失败并出现"Failed to find Server Action"错误。
.env.local 或密钥NEXT_PUBLIC_ 前缀NEXT_SERVER_ACTIONS_ENCRYPTION_KEYNEXT_TELEMETRY_DISABLED=1 禁用遥测poweredByHeaderoutput: 'standalone' 以获得最小的 Docker 镜像next/image 进行优化的图片处理latest、staging)输入: 使用 Docker 和 GitHub Actions 设置生产环境部署
输出:
// 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
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"]
# .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 }}
输入: 为暂存和生产环境配置不同的 API URL
输出:
// 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
}
// 在服务器组件中使用
import { getServerEnv } from '@/lib/env'
async function fetchData() {
const env = getServerEnv()
// 使用 env.DATABASE_URL
}
// 在客户端组件中使用
import { getPublicEnv } from '@/lib/env'
function ApiClient() {
const env = getPublicEnv()
// 使用 env.NEXT_PUBLIC_API_URL
}
# 用于本地开发的 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
输入: 向 Next.js 应用程序添加分布式追踪
输出:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation.node')
}
}
// 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()
// 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()
}
})
}
output: 'standalone'NEXT_PUBLIC_ 前缀NEXT_SERVER_ACTIONS_ENCRYPTION_KEYoutput: 'export')查阅这些文件以获取详细模式:
每周安装次数
221
仓库
GitHub 星标数
174
首次出现
2026年2月20日
安全审计
安装于
codex200
gemini-cli198
github-copilot195
cursor193
opencode193
kimi-cli192
Deploy Next.js applications to production with Docker, CI/CD pipelines, and comprehensive monitoring.
This skill provides patterns for:
Activate when user requests involve:
| Mode | Use Case | Command |
|---|---|---|
standalone | Docker/container deployment | output: 'standalone' |
export | Static site (no server) | output: 'export' |
| (default) | Node.js server deployment | next start |
| Prefix | Availability | Use Case |
|---|---|---|
NEXT_PUBLIC_ | Build-time + Browser | Public API keys, feature flags |
| (no prefix) | Server-only | Database URLs, secrets |
| Runtime | Server-only | Different values per environment |
| File | Purpose |
|---|---|
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 |
Enable standalone output for optimized Docker deployments:
// 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
Build optimized Docker image with minimal footprint:
# syntax=docker/dockerfile:1
FROM node:20-alpine AS base
# Install dependencies only when needed
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
# Rebuild the source code only when needed
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
# Generate build ID from git (set during build)
ARG GIT_HASH
ENV GIT_HASH=${GIT_HASH}
# Server Actions encryption key (CRITICAL for multi-server deployments)
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
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 public files if they exist
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"]
Create automated build and deployment pipeline:
# .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
// 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
For runtime variables with Docker, use a single image across environments:
// 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(', ')}`)
}
}
# .env.local (development - never commit)
DATABASE_URL=postgresql://localhost:5432/mydb
API_KEY=dev-key
NEXT_PUBLIC_API_URL=http://localhost:3000/api
# .env.production (production defaults)
NEXT_PUBLIC_API_URL=https://api.example.com
# .env.example (template for developers)
DATABASE_URL=
API_KEY=
NEXT_PUBLIC_API_URL=
Create a health check endpoint for load balancers and orchestrators:
// 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`,
}
}
Add observability with OpenTelemetry:
// 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,
})
}
// 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))
})
// 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)
}
}
Set up preview environments for pull requests:
# .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 }}"
CRITICAL : For multi-server deployments, set a consistent encryption key:
# Generate a key locally
openssl rand -base64 32
# Set in GitHub Actions (Secret)
# NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
# Or generate in workflow (see GitHub Actions example above)
# In Dockerfile - pass as build arg
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.
.env.local or secretsNEXT_PUBLIC_ prefix only for truly public valuesNEXT_SERVER_ACTIONS_ENCRYPTION_KEY for multi-server deploymentsNEXT_TELEMETRY_DISABLED=1poweredByHeader in productionoutput: 'standalone' for minimal Docker imagesnext/image for optimized imageslatest, staging)Input: Set up production deployment with Docker and GitHub Actions
Output:
// 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
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"]
# .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 }}
Input: Configure different API URLs for staging and production
Output:
// 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
}
# docker-compose.yml for local development
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://db:5432/myapp
- NEXT_PUBLIC_API_URL=http://localhost:3000/api
Input: Add distributed tracing to Next.js application
Output:
// instrumentation.ts
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./instrumentation.node')
}
}
// 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()
// 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()
}
})
}
output: 'standalone'NEXT_PUBLIC_ prefix for sensitive valuesNEXT_SERVER_ACTIONS_ENCRYPTION_KEY for multi-server deploymentsoutput: 'export')Consult these files for detailed patterns:
Weekly Installs
221
Repository
GitHub Stars
174
First Seen
Feb 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex200
gemini-cli198
github-copilot195
cursor193
opencode193
kimi-cli192
Azure Data Explorer (Kusto) 查询技能:KQL数据分析、日志遥测与时间序列处理
114,200 周安装
CLI-Anything:基于Codex的通用命令行工具构建框架,自动化生成CLI套件
626 周安装
LangChain AI社交内容创作技能:自动化生成LinkedIn/Twitter图文帖
635 周安装
stop-slop:AI写作优化工具 - 消除陈词滥调,提升文本真实性与可读性
656 周安装
智能外联草拟工具:基于调研的个性化邮件与LinkedIn消息生成器 | 销售与营销自动化
658 周安装
MarsWaveAI TTS:文本转语音API,支持多说话人脚本与快速语音合成
654 周安装
Office转Markdown工具:Word/Excel/PPT/PDF一键转换,支持AI增强处理
684 周安装