如何为不同环境(开发、预览、生产)配置不同的变量?

53 阅读4分钟

如何为不同环境(开发、预览、生产)配置不同的变量?

环境配置策略

1. 环境变量文件

Next.js 支持按环境自动加载不同的环境变量文件:

# .env.local (最高优先级,所有环境)
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
JWT_SECRET=your-secret-key

# .env.development (开发环境)
DEBUG=true
LOG_LEVEL=debug
API_URL=http://localhost:3001
NEXT_PUBLIC_API_URL=http://localhost:3001

# .env.production (生产环境)
DEBUG=false
LOG_LEVEL=error
API_URL=https://api.example.com
NEXT_PUBLIC_API_URL=https://api.example.com

# .env.preview (预览环境)
DEBUG=true
LOG_LEVEL=info
API_URL=https://preview-api.example.com
NEXT_PUBLIC_API_URL=https://preview-api.example.com

2. 环境检测

// lib/env.js
export const isDevelopment = process.env.NODE_ENV === 'development'
export const isProduction = process.env.NODE_ENV === 'production'
export const isPreview = process.env.VERCEL_ENV === 'preview'

export const getEnvironment = () => {
  if (isDevelopment) return 'development'
  if (isPreview) return 'preview'
  if (isProduction) return 'production'
  return 'unknown'
}

配置管理

1. 统一配置对象

// lib/config.js
const baseConfig = {
  appName: 'My App',
  version: '1.0.0',
}

const developmentConfig = {
  ...baseConfig,
  apiUrl: 'http://localhost:3001',
  databaseUrl: 'postgresql://user:password@localhost:5432/mydb_dev',
  debug: true,
  logLevel: 'debug',
  features: {
    analytics: false,
    monitoring: false,
    caching: false,
  },
}

const previewConfig = {
  ...baseConfig,
  apiUrl: 'https://preview-api.example.com',
  databaseUrl: 'postgresql://user:password@preview-db:5432/mydb_preview',
  debug: true,
  logLevel: 'info',
  features: {
    analytics: true,
    monitoring: true,
    caching: true,
  },
}

const productionConfig = {
  ...baseConfig,
  apiUrl: 'https://api.example.com',
  databaseUrl: 'postgresql://user:password@prod-db:5432/mydb_prod',
  debug: false,
  logLevel: 'error',
  features: {
    analytics: true,
    monitoring: true,
    caching: true,
  },
}

export const getConfig = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  if (env === 'development') return developmentConfig
  if (vercelEnv === 'preview') return previewConfig
  if (env === 'production') return productionConfig

  return developmentConfig
}

export const config = getConfig()

2. 环境特定配置

// lib/env-specific.js
export const getApiUrl = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  if (env === 'development') {
    return process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001'
  }

  if (vercelEnv === 'preview') {
    return process.env.NEXT_PUBLIC_API_URL || 'https://preview-api.example.com'
  }

  if (env === 'production') {
    return process.env.NEXT_PUBLIC_API_URL || 'https://api.example.com'
  }

  return 'http://localhost:3001'
}

export const getDatabaseUrl = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  if (env === 'development') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@localhost:5432/mydb_dev'
    )
  }

  if (vercelEnv === 'preview') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@preview-db:5432/mydb_preview'
    )
  }

  if (env === 'production') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@prod-db:5432/mydb_prod'
    )
  }

  return 'postgresql://user:password@localhost:5432/mydb_dev'
}

实际应用示例

1. 数据库配置

// lib/database.js
import { PrismaClient } from '@prisma/client'

const getDatabaseUrl = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  if (env === 'development') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@localhost:5432/mydb_dev'
    )
  }

  if (vercelEnv === 'preview') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@preview-db:5432/mydb_preview'
    )
  }

  if (env === 'production') {
    return (
      process.env.DATABASE_URL ||
      'postgresql://user:password@prod-db:5432/mydb_prod'
    )
  }

  return 'postgresql://user:password@localhost:5432/mydb_dev'
}

const globalForPrisma = globalThis

export const db =
  globalForPrisma.prisma ||
  new PrismaClient({
    datasources: {
      db: {
        url: getDatabaseUrl(),
      },
    },
    log:
      process.env.NODE_ENV === 'development'
        ? ['query', 'error', 'warn']
        : ['error'],
  })

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = db
}

2. API 客户端配置

// lib/api-client.js
const getApiConfig = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  const baseConfig = {
    timeout: 10000,
    retries: 3,
  }

  if (env === 'development') {
    return {
      ...baseConfig,
      baseURL: 'http://localhost:3001',
      timeout: 30000, // 开发环境更长的超时时间
      retries: 1,
    }
  }

  if (vercelEnv === 'preview') {
    return {
      ...baseConfig,
      baseURL: 'https://preview-api.example.com',
      timeout: 15000,
    }
  }

  if (env === 'production') {
    return {
      ...baseConfig,
      baseURL: 'https://api.example.com',
      timeout: 10000,
    }
  }

  return baseConfig
}

export const apiClient = axios.create(getApiConfig())

3. 日志配置

// lib/logger.js
const getLogLevel = () => {
  const env = process.env.NODE_ENV
  const vercelEnv = process.env.VERCEL_ENV

  if (env === 'development') return 'debug'
  if (vercelEnv === 'preview') return 'info'
  if (env === 'production') return 'error'

  return 'info'
}

export const logger = {
  debug: (message, ...args) => {
    if (getLogLevel() === 'debug') {
      console.debug(`[DEBUG] ${message}`, ...args)
    }
  },

  info: (message, ...args) => {
    if (['debug', 'info'].includes(getLogLevel())) {
      console.info(`[INFO] ${message}`, ...args)
    }
  },

  error: (message, ...args) => {
    console.error(`[ERROR] ${message}`, ...args)
  },
}

部署配置

1. Vercel 环境配置

# vercel.json
{
  "env": {
    "DATABASE_URL": "@database-url",
    "JWT_SECRET": "@jwt-secret"
  },
  "build": {
    "env": {
      "NEXT_PUBLIC_API_URL": "@api-url"
    }
  }
}
# 设置环境变量
vercel env add DATABASE_URL
vercel env add JWT_SECRET
vercel env add NEXT_PUBLIC_API_URL

# 为不同环境设置不同值
vercel env add DATABASE_URL --environment development
vercel env add DATABASE_URL --environment preview
vercel env add DATABASE_URL --environment production

2. Docker 环境配置

# Dockerfile
FROM node:18-alpine AS base

# 开发环境
FROM base AS development
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]

# 生产环境
FROM base AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
  app-dev:
    build:
      context: .
      target: development
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://user:password@localhost:5432/mydb_dev
    ports:
      - '3000:3000'

  app-prod:
    build:
      context: .
      target: production
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://user:password@prod-db:5432/mydb_prod
    ports:
      - '3001:3000'

3. 环境验证

// lib/env-validation.js
import { z } from 'zod'

const baseSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
})

const developmentSchema = baseSchema.extend({
  DEBUG: z.boolean().optional(),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).optional(),
})

const productionSchema = baseSchema.extend({
  DEBUG: z.literal(false),
  LOG_LEVEL: z.enum(['error']).optional(),
})

export const validateEnv = () => {
  const env = process.env.NODE_ENV

  try {
    if (env === 'development') {
      return developmentSchema.parse(process.env)
    }

    if (env === 'production') {
      return productionSchema.parse(process.env)
    }

    return baseSchema.parse(process.env)
  } catch (error) {
    console.error('Environment validation failed:', error)
    throw new Error('Invalid environment configuration')
  }
}

最佳实践

1. 配置模板

# .env.example
# 开发环境
NODE_ENV=development
DATABASE_URL=postgresql://user:password@localhost:5432/mydb_dev
JWT_SECRET=your-development-secret
NEXT_PUBLIC_API_URL=http://localhost:3001

# 预览环境
# NODE_ENV=production
# VERCEL_ENV=preview
# DATABASE_URL=postgresql://user:password@preview-db:5432/mydb_preview
# JWT_SECRET=your-preview-secret
# NEXT_PUBLIC_API_URL=https://preview-api.example.com

# 生产环境
# NODE_ENV=production
# DATABASE_URL=postgresql://user:password@prod-db:5432/mydb_prod
# JWT_SECRET=your-production-secret
# NEXT_PUBLIC_API_URL=https://api.example.com

2. 配置管理

// lib/config-manager.js
export class ConfigManager {
  constructor() {
    this.config = this.loadConfig()
  }

  loadConfig() {
    const env = process.env.NODE_ENV
    const vercelEnv = process.env.VERCEL_ENV

    const baseConfig = {
      appName: 'My App',
      version: '1.0.0',
    }

    if (env === 'development') {
      return {
        ...baseConfig,
        apiUrl: 'http://localhost:3001',
        debug: true,
        logLevel: 'debug',
      }
    }

    if (vercelEnv === 'preview') {
      return {
        ...baseConfig,
        apiUrl: 'https://preview-api.example.com',
        debug: true,
        logLevel: 'info',
      }
    }

    if (env === 'production') {
      return {
        ...baseConfig,
        apiUrl: 'https://api.example.com',
        debug: false,
        logLevel: 'error',
      }
    }

    return baseConfig
  }

  get(key) {
    return this.config[key]
  }

  getAll() {
    return this.config
  }
}

export const configManager = new ConfigManager()

总结

环境配置管理要点:

环境变量文件

  • .env.local (最高优先级)
  • .env.development / .env.production
  • .env.preview (预览环境)

配置策略

  • 统一配置对象
  • 环境特定配置
  • 配置验证

最佳实践

  • 使用配置模板
  • 环境变量验证
  • 配置管理类
  • 部署环境配置

安全考虑

  • 敏感信息不要使用 NEXT_PUBLIC_ 前缀
  • 不同环境使用不同的密钥
  • 定期轮换生产环境密钥