Appearance
环境变量管理
课程概述
本章节深入探讨前端项目中的环境变量管理,学习如何在不同环境(开发、测试、生产)中使用和管理配置信息。合理的环境变量管理能够提高项目的可维护性和安全性。
学习目标
- 理解环境变量的概念和作用
- 掌握不同构建工具中的环境变量配置
- 学习环境变量的最佳实践
- 了解敏感信息的安全管理
- 掌握多环境配置策略
- 学习环境变量的类型安全
第一部分:环境变量基础
1.1 什么是环境变量
环境变量是在应用程序运行时可以访问的键值对配置,用于存储不同环境下的配置信息。
作用:
javascript
1. 区分不同环境(开发、测试、生产)
2. 存储 API 端点、密钥等配置
3. 控制功能开关
4. 配置第三方服务
5. 管理构建选项常见用途:
javascript
// API 端点
API_URL=https://api.example.com
// 功能开关
ENABLE_ANALYTICS=true
ENABLE_DEBUG=false
// 第三方服务
STRIPE_API_KEY=pk_live_xxx
GOOGLE_ANALYTICS_ID=UA-xxx
// 构建配置
PUBLIC_PATH=/app/1.2 环境变量的类型
1. 构建时环境变量:
javascript
// 在构建过程中被替换
const apiUrl = process.env.REACT_APP_API_URL
// 构建后代码
const apiUrl = "https://api.example.com"2. 运行时环境变量:
javascript
// 服务器端环境变量
const dbUrl = process.env.DATABASE_URL // Node.js
// 客户端无法访问3. 公开环境变量:
javascript
// 打包到客户端代码中
VITE_API_URL=https://api.example.com
REACT_APP_VERSION=1.0.04. 私密环境变量:
javascript
// 仅在服务器端使用
DATABASE_URL=postgresql://...
SECRET_KEY=xxx
API_SECRET=yyy1.3 安全注意事项
javascript
// ❌ 错误:暴露敏感信息
VITE_SECRET_KEY=abc123
REACT_APP_API_SECRET=secret123
// ✅ 正确:仅暴露公开信息
VITE_API_URL=https://api.example.com
REACT_APP_APP_NAME=MyApp
// ✅ 私密信息仅在服务器端
DATABASE_URL=postgresql://... // 不要用 VITE_ 或 REACT_APP_ 前缀第二部分:Vite 环境变量
2.1 环境变量文件
Vite 使用 dotenv 加载环境变量:
bash
.env # 所有环境
.env.local # 所有环境,git 忽略
.env.development # 开发环境
.env.development.local
.env.production # 生产环境
.env.production.local
.env.test # 测试环境
.env.test.local加载优先级:
.env.[mode].local > .env.[mode] > .env.local > .env2.2 定义环境变量
bash
# .env
VITE_APP_TITLE=My App
VITE_API_BASE_URL=https://api.example.com
# .env.development
VITE_API_BASE_URL=http://localhost:3001
VITE_ENABLE_MOCK=true
VITE_LOG_LEVEL=debug
# .env.production
VITE_API_BASE_URL=https://api.production.com
VITE_ENABLE_MOCK=false
VITE_LOG_LEVEL=error
# .env.staging
VITE_API_BASE_URL=https://api.staging.com
VITE_ENABLE_MOCK=false
VITE_LOG_LEVEL=warn注意事项:
bash
# ✅ 会暴露给客户端(以 VITE_ 开头)
VITE_APP_NAME=MyApp
# ❌ 不会暴露给客户端
APP_NAME=MyApp
SECRET_KEY=xxx
# ✅ 特殊变量
MODE=development # Vite 内置
BASE_URL=/ # Vite 内置
PROD=false # Vite 内置
DEV=true # Vite 内置2.3 使用环境变量
typescript
// 在代码中访问
const apiUrl = import.meta.env.VITE_API_BASE_URL
const appTitle = import.meta.env.VITE_APP_TITLE
const mode = import.meta.env.MODE
const isDev = import.meta.env.DEV
const isProd = import.meta.env.PROD
console.log('API URL:', apiUrl)
console.log('App Title:', appTitle)
console.log('Mode:', mode)
console.log('Is Dev:', isDev)内置环境变量:
typescript
import.meta.env.MODE // 应用运行模式
import.meta.env.BASE_URL // 部署基础路径
import.meta.env.PROD // 是否生产环境
import.meta.env.DEV // 是否开发环境
import.meta.env.SSR // 是否服务端渲染2.4 TypeScript 类型定义
typescript
// src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
readonly VITE_API_BASE_URL: string
readonly VITE_ENABLE_MOCK: string
readonly VITE_LOG_LEVEL: 'debug' | 'info' | 'warn' | 'error'
readonly VITE_STRIPE_KEY: string
readonly VITE_GA_ID: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}2.5 配置文件中使用
typescript
// vite.config.ts
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// 加载环境变量
const env = loadEnv(mode, process.cwd(), '')
return {
// 使用环境变量
base: env.VITE_BASE_URL || '/',
server: {
port: parseInt(env.VITE_PORT) || 3000,
proxy: {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true
}
}
},
define: {
__APP_VERSION__: JSON.stringify(env.npm_package_version)
}
}
})2.6 自定义环境变量前缀
typescript
// vite.config.ts
export default defineConfig({
envPrefix: ['VITE_', 'APP_'], // 默认只有 VITE_
})bash
# .env
VITE_API_URL=xxx # 可访问
APP_NAME=xxx # 可访问
OTHER_VAR=xxx # 不可访问第三部分:Create React App 环境变量
3.1 CRA 环境变量规则
bash
# .env
REACT_APP_API_URL=https://api.example.com
REACT_APP_APP_NAME=MyApp
# .env.development
REACT_APP_API_URL=http://localhost:3001
# .env.production
REACT_APP_API_URL=https://api.production.com访问环境变量:
javascript
const apiUrl = process.env.REACT_APP_API_URL
const appName = process.env.REACT_APP_APP_NAME
const nodeEnv = process.env.NODE_ENV
console.log(apiUrl, appName, nodeEnv)3.2 内置环境变量
javascript
process.env.NODE_ENV // 'development' | 'production' | 'test'
process.env.PUBLIC_URL // public 目录的 URL3.3 扩展环境变量
bash
npm install --save-dev dotenv-expandbash
# .env
REACT_APP_BASE_URL=https://api.example.com
REACT_APP_API_URL=${REACT_APP_BASE_URL}/v1
REACT_APP_WEBSOCKET_URL=wss://${REACT_APP_BASE_URL}/ws第四部分:Next.js 环境变量
4.1 环境变量类型
1. 浏览器环境变量(公开):
bash
# .env
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=UA-xxxxxtypescript
// 客户端和服务端都可访问
const apiUrl = process.env.NEXT_PUBLIC_API_URL2. 服务器环境变量(私密):
bash
# .env
DATABASE_URL=postgresql://...
SECRET_KEY=xxx
API_SECRET=yyytypescript
// 仅服务端可访问
const dbUrl = process.env.DATABASE_URL4.2 环境变量文件
bash
.env # 所有环境
.env.local # 所有环境,git 忽略
.env.development # 开发环境
.env.development.local
.env.production # 生产环境
.env.production.local
.env.test # 测试环境(jest)
.env.test.local4.3 使用示例
typescript
// pages/api/data.ts - 服务端 API
export default async function handler(req, res) {
// 可以访问所有环境变量
const dbUrl = process.env.DATABASE_URL
const apiKey = process.env.API_SECRET
// ...
}
// components/App.tsx - 客户端组件
export function App() {
// 只能访问 NEXT_PUBLIC_ 开头的变量
const apiUrl = process.env.NEXT_PUBLIC_API_URL
return <div>API: {apiUrl}</div>
}4.4 运行时配置
typescript
// next.config.js
module.exports = {
env: {
customKey: 'customValue',
},
// 公开运行时配置
publicRuntimeConfig: {
apiUrl: process.env.API_URL,
},
// 服务端运行时配置
serverRuntimeConfig: {
secretKey: process.env.SECRET_KEY,
},
}typescript
// 使用运行时配置
import getConfig from 'next/config'
const { publicRuntimeConfig, serverRuntimeConfig } = getConfig()
console.log(publicRuntimeConfig.apiUrl)第五部分:Webpack 环境变量
5.1 DefinePlugin
javascript
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'__DEV__': JSON.stringify(true),
'__VERSION__': JSON.stringify('1.0.0'),
})
]
}5.2 EnvironmentPlugin
javascript
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
API_URL: 'http://localhost:3001',
DEBUG: false
})
]
}等同于:
javascript
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.API_URL': JSON.stringify(process.env.API_URL || 'http://localhost:3001'),
'process.env.DEBUG': JSON.stringify(process.env.DEBUG || false),
})5.3 dotenv-webpack
bash
npm install -D dotenv-webpackjavascript
// webpack.config.js
const Dotenv = require('dotenv-webpack')
module.exports = {
plugins: [
new Dotenv({
path: './.env',
safe: true, // 加载 .env.example
systemvars: true, // 加载系统环境变量
silent: false, // 隐藏错误
defaults: false // 加载 .env.defaults
})
]
}第六部分:环境变量最佳实践
6.1 环境变量结构
推荐的文件结构:
bash
.env # 默认配置(提交到 git)
.env.local # 本地覆盖(不提交)
.env.development # 开发环境(提交到 git)
.env.development.local # 开发本地(不提交)
.env.production # 生产环境(提交到 git)
.env.production.local # 生产本地(不提交)
.env.example # 示例文件(提交到 git).env.example 示例:
bash
# .env.example
# API Configuration
VITE_API_BASE_URL=
VITE_API_TIMEOUT=
# Feature Flags
VITE_ENABLE_ANALYTICS=
VITE_ENABLE_DEBUG=
# Third-party Services
VITE_STRIPE_KEY=
VITE_GA_ID=.gitignore 配置:
bash
# .gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*.local6.2 环境变量封装
创建统一的配置管理:
typescript
// src/config/env.ts
interface EnvConfig {
apiBaseUrl: string
apiTimeout: number
enableAnalytics: boolean
enableDebug: boolean
stripeKey: string
gaId: string
}
class Environment {
private config: EnvConfig
constructor() {
this.config = this.loadConfig()
this.validate()
}
private loadConfig(): EnvConfig {
return {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL || '',
apiTimeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000'),
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
enableDebug: import.meta.env.VITE_ENABLE_DEBUG === 'true',
stripeKey: import.meta.env.VITE_STRIPE_KEY || '',
gaId: import.meta.env.VITE_GA_ID || '',
}
}
private validate() {
const required = ['apiBaseUrl', 'stripeKey']
for (const key of required) {
if (!this.config[key as keyof EnvConfig]) {
throw new Error(`Missing required environment variable: ${key}`)
}
}
}
get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {
return this.config[key]
}
isDevelopment(): boolean {
return import.meta.env.DEV
}
isProduction(): boolean {
return import.meta.env.PROD
}
getMode(): string {
return import.meta.env.MODE
}
}
export const env = new Environment()使用封装的配置:
typescript
// 使用
import { env } from '@/config/env'
const apiUrl = env.get('apiBaseUrl')
const timeout = env.get('apiTimeout')
if (env.isDevelopment()) {
console.log('Development mode')
}6.3 类型安全的环境变量
typescript
// src/config/env.config.ts
import { z } from 'zod'
const envSchema = z.object({
VITE_API_BASE_URL: z.string().url(),
VITE_API_TIMEOUT: z.string().transform(Number).pipe(z.number().positive()),
VITE_ENABLE_ANALYTICS: z.enum(['true', 'false']).transform(val => val === 'true'),
VITE_ENABLE_DEBUG: z.enum(['true', 'false']).transform(val => val === 'true'),
VITE_STRIPE_KEY: z.string().min(1),
VITE_GA_ID: z.string().optional(),
})
type EnvConfig = z.infer<typeof envSchema>
function validateEnv(): EnvConfig {
try {
return envSchema.parse(import.meta.env)
} catch (error) {
console.error('Invalid environment variables:', error)
throw new Error('Environment validation failed')
}
}
export const env = validateEnv()6.4 功能开关管理
typescript
// src/config/features.ts
import { env } from './env'
export const features = {
analytics: {
enabled: env.get('enableAnalytics'),
id: env.get('gaId'),
},
debug: {
enabled: env.get('enableDebug'),
logLevel: import.meta.env.VITE_LOG_LEVEL || 'info',
},
experimental: {
newUI: import.meta.env.VITE_FEATURE_NEW_UI === 'true',
betaFeatures: import.meta.env.VITE_FEATURE_BETA === 'true',
},
integrations: {
stripe: {
enabled: !!env.get('stripeKey'),
key: env.get('stripeKey'),
},
},
}使用功能开关:
typescript
import { features } from '@/config/features'
function App() {
return (
<div>
{features.analytics.enabled && (
<Analytics id={features.analytics.id} />
)}
{features.experimental.newUI && (
<NewUI />
)}
{features.integrations.stripe.enabled && (
<StripeProvider apiKey={features.integrations.stripe.key}>
<PaymentForm />
</StripeProvider>
)}
</div>
)
}6.5 多环境配置
typescript
// src/config/environments.ts
type Environment = 'development' | 'staging' | 'production'
interface EnvironmentConfig {
apiUrl: string
wsUrl: string
cdnUrl: string
logLevel: 'debug' | 'info' | 'warn' | 'error'
}
const configs: Record<Environment, EnvironmentConfig> = {
development: {
apiUrl: 'http://localhost:3001',
wsUrl: 'ws://localhost:3001',
cdnUrl: 'http://localhost:3001/static',
logLevel: 'debug',
},
staging: {
apiUrl: 'https://api.staging.example.com',
wsUrl: 'wss://api.staging.example.com',
cdnUrl: 'https://cdn.staging.example.com',
logLevel: 'info',
},
production: {
apiUrl: 'https://api.example.com',
wsUrl: 'wss://api.example.com',
cdnUrl: 'https://cdn.example.com',
logLevel: 'error',
},
}
export function getConfig(): EnvironmentConfig {
const env = (import.meta.env.MODE || 'development') as Environment
return configs[env] || configs.development
}第七部分:安全管理
7.1 敏感信息处理
不要在客户端暴露:
bash
# ❌ 错误
VITE_DATABASE_URL=postgresql://...
VITE_SECRET_KEY=xxx
REACT_APP_API_SECRET=yyy
# ✅ 正确
DATABASE_URL=postgresql://...
SECRET_KEY=xxx
API_SECRET=yyy使用环境变量服务:
typescript
// 使用 AWS Secrets Manager
import { SecretsManager } from '@aws-sdk/client-secrets-manager'
async function getSecret(secretName: string) {
const client = new SecretsManager({ region: 'us-east-1' })
const response = await client.getSecretValue({
SecretId: secretName,
})
return JSON.parse(response.SecretString || '{}')
}7.2 本地开发安全
bash
# .env.local (不提交到 git)
VITE_API_KEY=local_dev_key
VITE_STRIPE_TEST_KEY=pk_test_xxx
# .env.production.local (部署时注入)
VITE_API_KEY=prod_key
VITE_STRIPE_LIVE_KEY=pk_live_xxx7.3 CI/CD 环境变量
GitHub Actions:
yaml
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
env:
VITE_API_URL: ${{ secrets.API_URL }}
VITE_STRIPE_KEY: ${{ secrets.STRIPE_KEY }}
run: npm run build
- name: Deploy
run: npm run deployVercel:
bash
# 添加环境变量
vercel env add VITE_API_URL production
vercel env add VITE_STRIPE_KEY productionNetlify:
toml
# netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
VITE_API_URL = "https://api.example.com"
[[context.production.environment]]
VITE_STRIPE_KEY = "pk_live_xxx"
[[context.deploy-preview.environment]]
VITE_STRIPE_KEY = "pk_test_xxx"第八部分:调试和故障排除
8.1 调试环境变量
typescript
// src/utils/debug-env.ts
export function debugEnv() {
if (import.meta.env.DEV) {
console.group('Environment Variables')
// 显示所有 VITE_ 开头的变量
Object.entries(import.meta.env).forEach(([key, value]) => {
if (key.startsWith('VITE_')) {
console.log(`${key}:`, value)
}
})
console.groupEnd()
}
}typescript
// main.tsx
import { debugEnv } from './utils/debug-env'
debugEnv()8.2 环境变量检查
typescript
// src/utils/check-env.ts
export function checkRequiredEnv(vars: string[]) {
const missing: string[] = []
for (const varName of vars) {
if (!import.meta.env[varName]) {
missing.push(varName)
}
}
if (missing.length > 0) {
throw new Error(
`Missing required environment variables:\n${missing.join('\n')}`
)
}
}
// 使用
checkRequiredEnv([
'VITE_API_BASE_URL',
'VITE_STRIPE_KEY',
])8.3 常见问题
1. 环境变量未生效:
bash
# 确保重启开发服务器
npm run dev
# 确保前缀正确
VITE_API_URL=xxx # Vite
REACT_APP_API_URL=xxx # CRA
NEXT_PUBLIC_API_URL=xxx # Next.js2. 类型错误:
typescript
// 使用类型断言
const timeout = parseInt(import.meta.env.VITE_TIMEOUT || '3000')
const enabled = import.meta.env.VITE_ENABLED === 'true'3. 环境变量被缓存:
bash
# 清除缓存
rm -rf node_modules/.vite
rm -rf .next
rm -rf dist
# 重新构建
npm run build第九部分:完整示例
9.1 环境配置系统
typescript
// src/config/index.ts
import { z } from 'zod'
// 定义 schema
const envSchema = z.object({
// API 配置
apiBaseUrl: z.string().url(),
apiTimeout: z.number().positive(),
// 功能开关
enableAnalytics: z.boolean(),
enableDebug: z.boolean(),
// 第三方服务
stripeKey: z.string().min(1),
gaId: z.string().optional(),
// 构建配置
mode: z.enum(['development', 'staging', 'production']),
version: z.string(),
})
type Config = z.infer<typeof envSchema>
// 加载和验证配置
function loadConfig(): Config {
const rawConfig = {
apiBaseUrl: import.meta.env.VITE_API_BASE_URL,
apiTimeout: parseInt(import.meta.env.VITE_API_TIMEOUT || '30000'),
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true',
enableDebug: import.meta.env.VITE_ENABLE_DEBUG === 'true',
stripeKey: import.meta.env.VITE_STRIPE_KEY,
gaId: import.meta.env.VITE_GA_ID,
mode: import.meta.env.MODE,
version: import.meta.env.VITE_APP_VERSION || '1.0.0',
}
try {
return envSchema.parse(rawConfig)
} catch (error) {
console.error('Configuration validation failed:', error)
throw error
}
}
export const config = loadConfig()
// 辅助函数
export const isDevelopment = config.mode === 'development'
export const isProduction = config.mode === 'production'
export const isStaging = config.mode === 'staging'9.2 使用示例
typescript
// src/services/api.ts
import axios from 'axios'
import { config } from '@/config'
const api = axios.create({
baseURL: config.apiBaseUrl,
timeout: config.apiTimeout,
})
// src/App.tsx
import { config, isProduction } from '@/config'
import { Analytics } from '@/components/Analytics'
function App() {
return (
<div>
{!isProduction && <DevTools />}
{config.enableAnalytics && config.gaId && (
<Analytics id={config.gaId} />
)}
<StripeProvider apiKey={config.stripeKey}>
<PaymentForm />
</StripeProvider>
</div>
)
}总结
本章全面介绍了环境变量管理:
- 基础概念 - 环境变量的作用和类型
- 工具配置 - Vite、CRA、Next.js、Webpack 的环境变量
- 最佳实践 - 结构、封装、类型安全
- 安全管理 - 敏感信息处理和 CI/CD 集成
- 调试技巧 - 问题诊断和解决方案
合理使用环境变量能够提高项目的可维护性、安全性和灵活性。