Nuxt 环境变量与配置管理完全指南

HTMLPAGE 团队
25分钟 分钟阅读

深入解析 Nuxt 3 中环境变量的配置方法、运行时配置、安全最佳实践。掌握开发、测试、生产多环境配置管理,实现灵活安全的应用配置体系。

#Nuxt #环境变量 #配置管理 #运行时配置 #安全实践

为什么需要良好的配置管理

在现代 Web 应用开发中,应用往往需要在不同环境(开发、测试、预发布、生产)中运行,每个环境都有其特定的配置需求:

  • API 端点:开发环境连接本地服务,生产环境连接线上服务
  • 密钥凭证:不同环境使用不同的 API Key、数据库密码
  • 功能开关:某些功能仅在特定环境启用
  • 日志级别:开发环境详细日志,生产环境精简日志

良好的配置管理能够:

  1. 提高安全性 - 敏感信息不硬编码在代码中
  2. 增强灵活性 - 无需修改代码即可调整配置
  3. 简化部署 - 一套代码适应多个环境
  4. 便于协作 - 团队成员可以有自己的本地配置

本文将深入解析 Nuxt 3 的配置管理体系,帮助你建立安全、灵活的配置方案。

Nuxt 3 配置体系概览

Nuxt 3 提供了多层次的配置体系:

┌─────────────────────────────────────────────────┐
│                  配置优先级                       │
├─────────────────────────────────────────────────┤
│  1. 运行时环境变量 (最高优先级)                    │
│     ↓                                           │
│  2. .env.{mode} 文件 (.env.production等)         │
│     ↓                                           │
│  3. .env.local 文件                              │
│     ↓                                           │
│  4. .env 文件                                    │
│     ↓                                           │
│  5. nuxt.config.ts 默认值 (最低优先级)            │
└─────────────────────────────────────────────────┘

配置类型对比

配置类型客户端可见服务端可见适用场景
runtimeConfig.public公开配置(API URL等)
runtimeConfig (私有)敏感信息(密钥等)
appConfig应用常量(主题等)
process.env (构建时)取决于使用位置构建配置

环境变量文件配置

基础 .env 文件

Nuxt 3 自动加载项目根目录的 .env 文件:

# .env - 基础环境变量(所有环境共享)

# 应用基本信息
NUXT_APP_NAME=我的应用
NUXT_APP_VERSION=1.0.0

# 公开配置(客户端可见)
NUXT_PUBLIC_API_BASE=http://localhost:3001
NUXT_PUBLIC_SITE_URL=http://localhost:3000
NUXT_PUBLIC_GA_ID=

# 私有配置(仅服务端)
NUXT_API_SECRET=dev-secret-key
NUXT_DATABASE_URL=postgresql://localhost:5432/myapp_dev

环境特定文件

创建环境特定的配置文件:

# .env.development - 开发环境
NUXT_PUBLIC_API_BASE=http://localhost:3001
NUXT_PUBLIC_DEBUG=true
NUXT_LOG_LEVEL=debug

# .env.production - 生产环境  
NUXT_PUBLIC_API_BASE=https://api.example.com
NUXT_PUBLIC_DEBUG=false
NUXT_LOG_LEVEL=error

# .env.staging - 预发布环境
NUXT_PUBLIC_API_BASE=https://staging-api.example.com
NUXT_PUBLIC_DEBUG=true
NUXT_LOG_LEVEL=info

本地覆盖文件

# .env.local - 本地开发覆盖(不提交到版本控制)
NUXT_API_SECRET=my-local-secret
NUXT_PUBLIC_API_BASE=http://192.168.1.100:3001

环境文件加载顺序

// Nuxt 加载环境文件的顺序(后加载的覆盖先加载的)
const loadOrder = [
  '.env',                    // 基础配置
  '.env.local',              // 本地覆盖
  `.env.${mode}`,            // 环境特定 (.env.development)
  `.env.${mode}.local`,      // 环境特定本地覆盖
]

.gitignore 配置

确保敏感文件不被提交:

# 环境变量文件
.env.local
.env.*.local
.env.production
.env.staging

# 保留示例文件
!.env.example

示例文件

创建 .env.example 作为模板:

# .env.example - 环境变量模板

# === 公开配置 ===
NUXT_PUBLIC_API_BASE=http://localhost:3001
NUXT_PUBLIC_SITE_URL=http://localhost:3000
NUXT_PUBLIC_GA_ID=

# === 私有配置 ===
# 数据库连接
NUXT_DATABASE_URL=postgresql://user:password@localhost:5432/dbname

# API 密钥
NUXT_API_SECRET=your-secret-key
NUXT_STRIPE_SECRET_KEY=sk_test_xxx

# 第三方服务
NUXT_SMTP_HOST=smtp.example.com
NUXT_SMTP_USER=
NUXT_SMTP_PASS=

运行时配置(runtimeConfig)

nuxt.config.ts 配置

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // 私有配置 - 仅服务端可访问
    apiSecret: '',
    databaseUrl: '',
    
    // 邮件服务配置
    smtp: {
      host: '',
      port: 587,
      user: '',
      pass: '',
    },
    
    // 第三方服务密钥
    stripe: {
      secretKey: '',
      webhookSecret: '',
    },
    
    // 公开配置 - 客户端和服务端都可访问
    public: {
      apiBase: 'http://localhost:3001',
      siteUrl: 'http://localhost:3000',
      appName: '我的应用',
      
      // 功能开关
      features: {
        enableAnalytics: false,
        enableChat: false,
        maintenanceMode: false,
      },
      
      // 分析服务
      analytics: {
        gaId: '',
        gtmId: '',
      },
    },
  },
})

环境变量自动映射

Nuxt 3 自动将 NUXT_ 前缀的环境变量映射到 runtimeConfig:

# 环境变量 → runtimeConfig 映射规则

# 私有配置(直接映射)
NUXT_API_SECRET → runtimeConfig.apiSecret
NUXT_DATABASE_URL → runtimeConfig.databaseUrl

# 嵌套配置(使用下划线分隔)
NUXT_SMTP_HOST → runtimeConfig.smtp.host
NUXT_SMTP_PORT → runtimeConfig.smtp.port
NUXT_STRIPE_SECRET_KEY → runtimeConfig.stripe.secretKey

# 公开配置(需要 PUBLIC 前缀)
NUXT_PUBLIC_API_BASE → runtimeConfig.public.apiBase
NUXT_PUBLIC_SITE_URL → runtimeConfig.public.siteUrl
NUXT_PUBLIC_FEATURES_ENABLE_ANALYTICS → runtimeConfig.public.features.enableAnalytics

在代码中使用配置

<script setup lang="ts">
// 获取运行时配置
const config = useRuntimeConfig()

// 公开配置 - 客户端和服务端都可用
console.log(config.public.apiBase)
console.log(config.public.appName)

// 私有配置 - 仅在服务端代码中可用
// 在客户端访问会返回 undefined
if (import.meta.server) {
  console.log(config.apiSecret)
  console.log(config.databaseUrl)
}
</script>

<template>
  <div>
    <h1>{{ config.public.appName }}</h1>
    <p>API: {{ config.public.apiBase }}</p>
  </div>
</template>

服务端 API 中使用

// server/api/users.get.ts
export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  
  // 安全地访问私有配置
  const apiSecret = config.apiSecret
  const dbUrl = config.databaseUrl
  
  // 使用配置进行 API 调用
  const response = await $fetch('/external-api/users', {
    baseURL: config.public.apiBase,
    headers: {
      'Authorization': `Bearer ${apiSecret}`,
    },
  })
  
  return response
})

Composable 封装

// composables/useApiConfig.ts
export function useApiConfig() {
  const config = useRuntimeConfig()
  
  return {
    baseURL: config.public.apiBase,
    siteURL: config.public.siteUrl,
    
    // 计算属性
    isProduction: computed(() => 
      config.public.siteUrl.includes('example.com')
    ),
    
    // 功能开关
    features: config.public.features,
    
    // 带认证的请求配置
    getAuthHeaders: () => {
      if (import.meta.server) {
        return {
          'X-API-Key': config.apiSecret,
        }
      }
      return {}
    },
  }
}

应用配置(appConfig)

app.config.ts 定义

与 runtimeConfig 不同,appConfig 用于非敏感的应用常量:

// app.config.ts
export default defineAppConfig({
  // 应用信息
  title: 'HTMLPAGE',
  description: '专业的前端开发知识平台',
  
  // 主题配置
  theme: {
    primaryColor: '#3B82F6',
    secondaryColor: '#10B981',
    darkMode: true,
    borderRadius: '0.5rem',
  },
  
  // 布局配置
  layout: {
    header: {
      height: 64,
      sticky: true,
    },
    sidebar: {
      width: 256,
      collapsible: true,
    },
    footer: {
      visible: true,
    },
  },
  
  // 功能配置
  features: {
    search: true,
    comments: true,
    newsletter: true,
    socialShare: ['twitter', 'facebook', 'linkedin'],
  },
  
  // SEO 默认配置
  seo: {
    defaultImage: '/og-image.png',
    twitterHandle: '@htmlpage',
    locale: 'zh-CN',
  },
})

类型定义

// types/app.config.d.ts
declare module 'nuxt/schema' {
  interface AppConfigInput {
    title?: string
    description?: string
    theme?: {
      primaryColor?: string
      secondaryColor?: string
      darkMode?: boolean
      borderRadius?: string
    }
    layout?: {
      header?: {
        height?: number
        sticky?: boolean
      }
      sidebar?: {
        width?: number
        collapsible?: boolean
      }
      footer?: {
        visible?: boolean
      }
    }
    features?: {
      search?: boolean
      comments?: boolean
      newsletter?: boolean
      socialShare?: string[]
    }
    seo?: {
      defaultImage?: string
      twitterHandle?: string
      locale?: string
    }
  }
}

export {}

在组件中使用

<script setup lang="ts">
const appConfig = useAppConfig()

// 响应式访问配置
const theme = computed(() => appConfig.theme)
const features = computed(() => appConfig.features)
</script>

<template>
  <header 
    :class="{ 'sticky top-0': appConfig.layout.header.sticky }"
    :style="{ height: `${appConfig.layout.header.height}px` }"
  >
    <h1 :style="{ color: appConfig.theme.primaryColor }">
      {{ appConfig.title }}
    </h1>
  </header>
  
  <aside 
    v-if="appConfig.layout.sidebar.collapsible"
    :style="{ width: `${appConfig.layout.sidebar.width}px` }"
  >
    <!-- 侧边栏内容 -->
  </aside>
</template>

运行时修改 appConfig

// composables/useTheme.ts
export function useTheme() {
  const appConfig = useAppConfig()
  
  const toggleDarkMode = () => {
    appConfig.theme.darkMode = !appConfig.theme.darkMode
  }
  
  const setPrimaryColor = (color: string) => {
    appConfig.theme.primaryColor = color
  }
  
  return {
    isDark: computed(() => appConfig.theme.darkMode),
    primaryColor: computed(() => appConfig.theme.primaryColor),
    toggleDarkMode,
    setPrimaryColor,
  }
}

多环境配置管理

环境检测

// composables/useEnvironment.ts
export function useEnvironment() {
  const config = useRuntimeConfig()
  
  // 环境判断
  const isDevelopment = process.dev
  const isProduction = !process.dev
  const isServer = import.meta.server
  const isClient = import.meta.client
  
  // 基于配置判断环境类型
  const environmentType = computed(() => {
    const siteUrl = config.public.siteUrl
    if (siteUrl.includes('localhost')) return 'local'
    if (siteUrl.includes('staging')) return 'staging'
    if (siteUrl.includes('preview')) return 'preview'
    return 'production'
  })
  
  return {
    isDevelopment,
    isProduction,
    isServer,
    isClient,
    environmentType,
  }
}

条件配置

// nuxt.config.ts
const isDev = process.env.NODE_ENV === 'development'
const isStaging = process.env.NUXT_ENV === 'staging'

export default defineNuxtConfig({
  // 开发环境配置
  ...(isDev && {
    devtools: { enabled: true },
    sourcemap: {
      client: true,
      server: true,
    },
  }),
  
  // 生产环境配置
  ...(!isDev && {
    sourcemap: false,
    nitro: {
      compressPublicAssets: true,
      minify: true,
    },
  }),
  
  // 运行时配置
  runtimeConfig: {
    public: {
      debug: isDev,
      environment: process.env.NUXT_ENV || 'development',
    },
  },
})

功能开关管理

// composables/useFeatureFlags.ts
interface FeatureFlags {
  newDashboard: boolean
  betaFeatures: boolean
  experimentalApi: boolean
  maintenanceMode: boolean
}

export function useFeatureFlags() {
  const config = useRuntimeConfig()
  const { environmentType } = useEnvironment()
  
  // 基础功能标志
  const baseFlags = computed<FeatureFlags>(() => ({
    newDashboard: config.public.features?.enableNewDashboard ?? false,
    betaFeatures: config.public.features?.enableBeta ?? false,
    experimentalApi: config.public.features?.enableExperimental ?? false,
    maintenanceMode: config.public.features?.maintenanceMode ?? false,
  }))
  
  // 环境特定覆盖
  const flags = computed<FeatureFlags>(() => {
    const base = baseFlags.value
    
    // 开发环境启用所有功能
    if (environmentType.value === 'local') {
      return {
        ...base,
        newDashboard: true,
        betaFeatures: true,
        experimentalApi: true,
      }
    }
    
    // 预发布环境启用测试功能
    if (environmentType.value === 'staging') {
      return {
        ...base,
        betaFeatures: true,
      }
    }
    
    return base
  })
  
  // 检查功能是否启用
  const isEnabled = (feature: keyof FeatureFlags): boolean => {
    return flags.value[feature]
  }
  
  return {
    flags,
    isEnabled,
  }
}

使用功能开关

<script setup lang="ts">
const { flags, isEnabled } = useFeatureFlags()
</script>

<template>
  <div>
    <!-- 维护模式提示 -->
    <MaintenanceBanner v-if="flags.maintenanceMode" />
    
    <!-- 新功能 -->
    <NewDashboard v-if="isEnabled('newDashboard')" />
    <LegacyDashboard v-else />
    
    <!-- Beta 功能入口 -->
    <BetaFeaturesPanel v-if="flags.betaFeatures" />
  </div>
</template>

安全最佳实践

敏感信息处理

// 1. 永远不要在公开配置中暴露敏感信息
// ❌ 错误做法
runtimeConfig: {
  public: {
    apiSecret: 'secret-key', // 会暴露到客户端!
  }
}

// ✅ 正确做法
runtimeConfig: {
  apiSecret: '', // 私有配置
  public: {
    apiBase: '', // 仅公开必要信息
  }
}

配置验证

// server/plugins/validate-config.ts
export default defineNitroPlugin((nitroApp) => {
  const config = useRuntimeConfig()
  
  // 必需配置检查
  const requiredConfigs = [
    { key: 'apiSecret', value: config.apiSecret },
    { key: 'databaseUrl', value: config.databaseUrl },
  ]
  
  for (const { key, value } of requiredConfigs) {
    if (!value) {
      console.error(`[Config Error] Missing required config: ${key}`)
      
      // 生产环境缺少配置时抛出错误
      if (!process.dev) {
        throw new Error(`Missing required configuration: ${key}`)
      }
    }
  }
  
  // 格式验证
  if (config.databaseUrl && !config.databaseUrl.startsWith('postgresql://')) {
    console.warn('[Config Warning] DATABASE_URL should start with postgresql://')
  }
  
  console.log('[Config] All configurations validated successfully')
})

配置加密

// utils/config-encryption.ts
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto'

const ALGORITHM = 'aes-256-gcm'

export function encryptConfig(value: string, key: string): string {
  const iv = randomBytes(16)
  const cipher = createCipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv)
  
  let encrypted = cipher.update(value, 'utf8', 'hex')
  encrypted += cipher.final('hex')
  
  const authTag = cipher.getAuthTag()
  
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`
}

export function decryptConfig(encrypted: string, key: string): string {
  const [ivHex, authTagHex, data] = encrypted.split(':')
  
  const iv = Buffer.from(ivHex, 'hex')
  const authTag = Buffer.from(authTagHex, 'hex')
  
  const decipher = createDecipheriv(ALGORITHM, Buffer.from(key, 'hex'), iv)
  decipher.setAuthTag(authTag)
  
  let decrypted = decipher.update(data, 'hex', 'utf8')
  decrypted += decipher.final('utf8')
  
  return decrypted
}

客户端配置过滤

// server/middleware/filter-config.ts
export default defineEventHandler((event) => {
  // 阻止客户端访问配置端点
  if (event.path.startsWith('/api/config') && !event.path.includes('public')) {
    throw createError({
      statusCode: 403,
      message: 'Access denied',
    })
  }
})

配置类型安全

完整类型定义

// types/runtime-config.d.ts
declare module 'nuxt/schema' {
  interface RuntimeConfig {
    // 私有配置
    apiSecret: string
    databaseUrl: string
    
    smtp: {
      host: string
      port: number
      user: string
      pass: string
      secure: boolean
    }
    
    stripe: {
      secretKey: string
      webhookSecret: string
    }
    
    redis: {
      host: string
      port: number
      password: string
    }
  }
  
  interface PublicRuntimeConfig {
    apiBase: string
    siteUrl: string
    appName: string
    appVersion: string
    
    features: {
      enableAnalytics: boolean
      enableChat: boolean
      maintenanceMode: boolean
      enableNewDashboard: boolean
      enableBeta: boolean
      enableExperimental: boolean
    }
    
    analytics: {
      gaId: string
      gtmId: string
    }
    
    social: {
      twitterHandle: string
      facebookAppId: string
    }
    
    debug: boolean
    environment: 'development' | 'staging' | 'production'
  }
}

export {}

类型安全的配置访问

// composables/useTypedConfig.ts
export function useTypedConfig() {
  const config = useRuntimeConfig()
  
  return {
    // 公开配置(类型安全)
    public: {
      apiBase: config.public.apiBase,
      siteUrl: config.public.siteUrl,
      appName: config.public.appName,
      features: config.public.features,
      analytics: config.public.analytics,
    },
    
    // 服务端配置访问器
    server: {
      getApiSecret: () => {
        if (!import.meta.server) {
          throw new Error('apiSecret can only be accessed on server')
        }
        return config.apiSecret
      },
      getDatabaseUrl: () => {
        if (!import.meta.server) {
          throw new Error('databaseUrl can only be accessed on server')
        }
        return config.databaseUrl
      },
    },
  }
}

部署配置

Docker 环境

# Dockerfile
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner

WORKDIR /app

# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nuxt

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

USER nuxt

# 暴露端口
EXPOSE 3000

# 启动命令
CMD ["node", ".output/server/index.mjs"]
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      # 基础配置
      - NUXT_PUBLIC_API_BASE=${API_BASE}
      - NUXT_PUBLIC_SITE_URL=${SITE_URL}
      
      # 私有配置
      - NUXT_API_SECRET=${API_SECRET}
      - NUXT_DATABASE_URL=${DATABASE_URL}
      
      # 功能开关
      - NUXT_PUBLIC_FEATURES_ENABLE_ANALYTICS=true
    env_file:
      - .env.production

Kubernetes 配置

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nuxt-config
data:
  NUXT_PUBLIC_API_BASE: "https://api.example.com"
  NUXT_PUBLIC_SITE_URL: "https://example.com"
  NUXT_PUBLIC_FEATURES_ENABLE_ANALYTICS: "true"

---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nuxt-secrets
type: Opaque
data:
  NUXT_API_SECRET: base64-encoded-secret
  NUXT_DATABASE_URL: base64-encoded-url

---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nuxt-app
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: nuxt
          image: my-nuxt-app:latest
          ports:
            - containerPort: 3000
          envFrom:
            - configMapRef:
                name: nuxt-config
            - secretRef:
                name: nuxt-secrets

Vercel/Netlify 配置

// vercel.json
{
  "build": {
    "env": {
      "NUXT_PUBLIC_API_BASE": "@api_base",
      "NUXT_PUBLIC_SITE_URL": "@site_url"
    }
  }
}
# netlify.toml
[build]
  command = "npm run build"
  publish = ".output/public"

[build.environment]
  NODE_VERSION = "20"

[context.production.environment]
  NUXT_PUBLIC_API_BASE = "https://api.example.com"

[context.deploy-preview.environment]
  NUXT_PUBLIC_API_BASE = "https://staging-api.example.com"

调试与问题排查

配置调试工具

// server/api/debug/config.get.ts
export default defineEventHandler((event) => {
  // 仅开发环境可用
  if (!process.dev) {
    throw createError({
      statusCode: 403,
      message: 'Debug endpoint only available in development',
    })
  }
  
  const config = useRuntimeConfig()
  
  return {
    // 显示公开配置
    public: config.public,
    
    // 私有配置仅显示是否已设置
    private: {
      apiSecret: config.apiSecret ? '✓ Set' : '✗ Not set',
      databaseUrl: config.databaseUrl ? '✓ Set' : '✗ Not set',
      'smtp.host': config.smtp?.host ? '✓ Set' : '✗ Not set',
    },
    
    // 环境信息
    environment: {
      nodeEnv: process.env.NODE_ENV,
      isDev: process.dev,
    },
  }
})

常见问题

// 问题1: 环境变量未生效
// 解决: 检查命名规范
// ❌ 错误: API_BASE (缺少 NUXT_ 前缀)
// ✅ 正确: NUXT_PUBLIC_API_BASE

// 问题2: 私有配置在客户端为 undefined
// 这是预期行为,私有配置仅服务端可用
const config = useRuntimeConfig()
// 客户端: config.apiSecret === undefined ✓

// 问题3: 嵌套配置映射
// NUXT_SMTP_HOST → config.smtp.host
// 注意: 需要在 nuxt.config.ts 中预定义嵌套结构

最佳实践总结

配置管理清单

实践说明
使用 .env.example提供配置模板,方便团队成员
敏感信息用私有配置密钥、密码等不放 public
类型定义为所有配置添加 TypeScript 类型
配置验证应用启动时验证必需配置
环境隔离不同环境使用不同配置文件
版本控制.env.local 等敏感文件加入 .gitignore

配置设计原则

  1. 最小暴露原则 - 只公开必要的配置
  2. 默认安全原则 - 默认值应该是安全的
  3. 环境隔离原则 - 环境间配置完全独立
  4. 类型安全原则 - 所有配置都有类型定义
  5. 验证优先原则 - 启动时验证所有必需配置

总结

Nuxt 3 提供了完善的配置管理体系:

  • 环境变量文件 - 支持多环境、自动加载、本地覆盖
  • 运行时配置 - 分离公开/私有配置,自动环境变量映射
  • 应用配置 - 非敏感常量,支持运行时修改
  • 类型安全 - 完整的 TypeScript 支持

通过合理使用这些配置机制,可以构建安全、灵活、易于维护的 Nuxt 应用。记住始终遵循安全最佳实践,保护敏感信息不被泄露到客户端。