为什么需要良好的配置管理
在现代 Web 应用开发中,应用往往需要在不同环境(开发、测试、预发布、生产)中运行,每个环境都有其特定的配置需求:
- API 端点:开发环境连接本地服务,生产环境连接线上服务
- 密钥凭证:不同环境使用不同的 API Key、数据库密码
- 功能开关:某些功能仅在特定环境启用
- 日志级别:开发环境详细日志,生产环境精简日志
良好的配置管理能够:
- 提高安全性 - 敏感信息不硬编码在代码中
- 增强灵活性 - 无需修改代码即可调整配置
- 简化部署 - 一套代码适应多个环境
- 便于协作 - 团队成员可以有自己的本地配置
本文将深入解析 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 |
配置设计原则
- 最小暴露原则 - 只公开必要的配置
- 默认安全原则 - 默认值应该是安全的
- 环境隔离原则 - 环境间配置完全独立
- 类型安全原则 - 所有配置都有类型定义
- 验证优先原则 - 启动时验证所有必需配置
总结
Nuxt 3 提供了完善的配置管理体系:
- 环境变量文件 - 支持多环境、自动加载、本地覆盖
- 运行时配置 - 分离公开/私有配置,自动环境变量映射
- 应用配置 - 非敏感常量,支持运行时修改
- 类型安全 - 完整的 TypeScript 支持
通过合理使用这些配置机制,可以构建安全、灵活、易于维护的 Nuxt 应用。记住始终遵循安全最佳实践,保护敏感信息不被泄露到客户端。


