环境变量管理
大白话解释: 环境变量就像"不同场合的配置"。开发环境用本地 API,生产环境用线上 API,测试环境用测试 API。不用每次都改代码,切换环境变量就行。
为什么要用环境变量?
- 安全性:密码、密钥等敏感信息不写在代码里,避免泄露
- 灵活性:不同环境用不同的配置,不用改代码
- 可维护性:配置集中管理,修改方便
环境变量的典型用途:
- API 地址(开发用 localhost,生产用线上域名)
- 调试开关(开发环境开启调试,生产环境关闭)
- 密钥信息(数据库密码、第三方服务密钥)
.env 文件的加载顺序:
.env(通用配置).env.local(本地覆盖,不提交 Git).env.[mode](环境特定配置).env.[mode].local(环境特定的本地覆盖)
前端项目中管理不同环境(开发、测试、生产)的配置变量。
环境变量类型
| 环境 | 说明 | 文件 |
|---|---|---|
| development | 本地开发 | .env.development |
| staging | 测试环境 | .env.staging |
| production | 生产环境 | .env.production |
| local | 本地覆盖(不提交 Git) | .env.local |
Vite 项目配置
文件命名规则
.env # 所有环境通用
.env.local # 本地覆盖(不提交 Git)
.env.development # 开发环境
.env.development.local # 开发环境本地覆盖
.env.production # 生产环境
.env.staging # 测试环境环境变量内容
bash
# .env(通用)
VITE_APP_TITLE=My App
VITE_APP_VERSION=1.0.0
# .env.development
VITE_API_URL=http://localhost:3000
VITE_DEBUG=true
# .env.production
VITE_API_URL=https://api.example.com
VITE_DEBUG=false
# .env.staging
VITE_API_URL=https://staging-api.example.com
VITE_DEBUG=true💡 Vite 的
.env文件由 dotenv 解析,支持#行注释和行尾注释。但建议将注释独立占一行,保持可读性。
使用方式
ts
// 代码中访问
console.log(import.meta.env.VITE_API_URL)
console.log(import.meta.env.MODE) // development / production
console.log(import.meta.env.PROD) // 是否是生产环境(boolean)
console.log(import.meta.env.DEV) // 是否是开发环境(boolean)
console.log(import.meta.env.SSR) // 是否是 SSR
console.log(import.meta.env.BASE_URL) // base 路由(vite.config 中的 base,默认 /)import.meta.env 内置变量
| 变量 | 类型 | 说明 |
|---|---|---|
MODE | string | 当前模式(development / production / staging) |
DEV | boolean | 是否开发环境 |
PROD | boolean | 是否生产环境 |
SSR | boolean | 是否服务端渲染 |
BASE_URL | string | base 路径,由 vite.config 的 base 配置决定 |
ts
// DEV 与 PROD 互斥
if (import.meta.env.DEV) {
console.log('开发环境')
}
if (import.meta.env.PROD) {
console.log('生产环境')
}
// BASE_URL 使用场景
const assetUrl = `${import.meta.env.BASE_URL}logo.png`
// base: '/' → /logo.png
// base: '/app/' → /app/logo.pngprocess.env 与 import.meta.env 的区别
| 特性 | process.env | import.meta.env |
|---|---|---|
| 来源 | Node.js 全局对象 | Vite 注入 |
| 适用场景 | Node.js / SSR 端 | 浏览器端 |
| 前缀过滤 | 无(全部暴露) | 仅 VITE_ 前缀 |
| 访问方式 | process.env.NODE_ENV | import.meta.env.MODE |
| Webpack 支持 | ✅(DefinePlugin) | ❌ |
| Vite 支持 | 仅 SSR 端 | ✅ |
ts
// Vite 项目中:浏览器端使用 import.meta.env,SSR 端可用 process.env
// Webpack 项目中:统一使用 process.env
// 常见错误:在 Vite 浏览器端使用 process.env
console.log(process.env.VITE_API_URL) // ❌ 浏览器中 process 未定义
console.log(import.meta.env.VITE_API_URL) // ✅类型声明
ts
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
readonly VITE_APP_VERSION: string
readonly VITE_DEBUG: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}构建命令
json
{
"scripts": {
"dev": "vite",
"build": "vite build",
"build:staging": "vite build --mode staging",
"build:production": "vite build --mode production"
}
}Node.js 项目配置
使用 dotenv
bash
npm install dotenvjs
// 入口文件顶部加载
require('dotenv').config()
// 或 ES Module
import 'dotenv/config'
// 使用
console.log(process.env.API_URL)
console.log(process.env.DB_PASSWORD)多环境配置
js
import dotenv from 'dotenv'
import path from 'path'
// 根据 NODE_ENV 加载不同文件
dotenv.config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV}`)
})dotenv-expand 变量扩展
bash
npm install dotenv-expandjs
import dotenv from 'dotenv'
import dotenvExpand from 'dotenv-expand'
const env = dotenv.config()
dotenvExpand.expand(env)bash
# .env 中引用其他变量
DOMAIN=example.com
API_URL=https://${DOMAIN}/api
BASE_URL=https://${DOMAIN}
# 结果:
# API_URL = https://example.com/api
# BASE_URL = https://example.com
# 支持默认值语法
PORT=${SERVER_PORT:-3000}
# 如果 SERVER_PORT 未定义,则 PORT = 3000cross-env 跨平台环境变量
bash
npm install cross-env --save-devjson
{
"scripts": {
"dev": "cross-env NODE_ENV=development node index.js",
"build": "cross-env NODE_ENV=production webpack --mode production",
"start:staging": "cross-env NODE_ENV=staging node server.js"
}
}text
# 为什么需要 cross-env
# Windows: set NODE_ENV=development && node index.js
# Linux/macOS: NODE_ENV=development node index.js
# cross-env 统一写法,自动适配操作系统.env 文件中引用其他变量
bash
# dotenv-expand 支持的语法
# 基本引用
DOMAIN=example.com
API_URL=https://${DOMAIN}/api
# 嵌套引用
PROTOCOL=https
HOST=example.com
API_URL=${PROTOCOL}://${HOST}/api
# 默认值(变量未定义时使用)
PORT=${SERVER_PORT:-3000}
LOG_LEVEL=${DEBUG:-info}
# 注意:Vite 内置支持变量扩展,无需额外安装 dotenv-expand
# Webpack 项目需要手动安装 dotenv-expandGit 配置
.gitignore
bash
# 提交
.env
.env.development
.env.production
.env.staging
# 不提交(本地覆盖)
.env.local
.env.*.local提交规范
| 文件 | 是否提交 | 原因 |
|---|---|---|
.env | ✅ | 通用配置,无敏感信息 |
.env.development | ✅ | 开发环境配置 |
.env.production | ❌ | 可能含敏感信息 |
.env.local | ❌ | 本地覆盖,个人配置 |
CI/CD 中的环境变量
GitHub Actions
yaml
# .github/workflows/deploy.yml — CI/CD 中使用环境变量
name: Build and Deploy
on:
push:
branches: [main] # 推送到 main 时触发
jobs:
build:
runs-on: ubuntu-latest # 运行环境
steps:
- uses: actions/checkout@v4 # 拉取代码
- name: Setup Node.js
uses: actions/setup-node@v4 # 安装 Node.js
with:
node-version: 20
cache: yarn # 启用 yarn 依赖缓存(加速构建)
- name: Install
run: yarn install --frozen-lockfile # 安装依赖
- name: Build
run: yarn build # 构建项目
env:
# 从 GitHub Secrets 读取敏感配置(仓库 Settings → Secrets and variables → Actions)
VITE_API_URL: ${{ secrets.VITE_API_URL }}
VITE_APP_TITLE: ${{ secrets.VITE_APP_TITLE }}
- name: Deploy
run: echo "Deploying..." # 部署步骤💡 在仓库 Settings → Secrets and variables → Actions 中添加 Secret。Secret 在日志中自动被掩码(
***),不会泄露。
Vercel
bash
# 控制台设置:Settings → Environment Variables
# 或 CLI
vercel env add VITE_API_URLNetlify
bash
# 控制台设置:Site settings → Environment variables
# 或 netlify.toml
[build.environment]
VITE_API_URL = "https://api.example.com"安全规范
敏感信息处理
bash
# ❌ 不要提交到 Git
DB_PASSWORD=secret123
API_KEY=sk-xxxx
# ✅ 使用环境变量管理
# 在 CI/CD 平台或服务器上设置Vite 安全限制
ts
// 只有 VITE_ 开头的变量会暴露给客户端
VITE_API_URL=https://api.example.com // ✅ 客户端可访问
API_SECRET=sk-xxxx // ❌ 客户端不可访问(安全)常见错误
bash
# ❌ 在 .env 中使用引号
VITE_API_URL="https://api.example.com" # 可能导致问题
# ✅ 不使用引号
VITE_API_URL=https://api.example.com最佳实践
环境变量分类
bash
# 公开配置(可提交)
VITE_APP_TITLE=My App
VITE_APP_VERSION=1.0.0
# 环境配置(可提交)
VITE_API_URL=http://localhost:3000
# 敏感配置(不提交)
VITE_API_KEY=sk-xxxx验证环境变量
ts
// 启动时验证必要环境变量
const requiredEnvVars = ['VITE_API_URL', 'VITE_APP_TITLE']
requiredEnvVars.forEach((key) => {
if (!import.meta.env[key]) {
throw new Error(`Missing required environment variable: ${key}`)
}
})Webpack 环境变量配置
Create React App (CRA) 配置
bash
# CRA 使用 REACT_APP_ 前缀(类似 Vite 的 VITE_)
# 只有 REACT_APP_ 开头的变量会暴露给浏览器端
# .env
REACT_APP_API_URL=https://api.example.com
REACT_APP_TITLE=My App
# ❌ 以下变量不会暴露到客户端
API_SECRET=sk-xxxx
DB_PASSWORD=secretts
// 代码中访问
console.log(process.env.REACT_APP_API_URL)
console.log(process.env.NODE_ENV) // CRA 内置,自动设置
// CRA 内置变量
console.log(process.env.NODE_ENV) // development / production / test
console.log(process.env.PUBLIC_URL) // public 目录路径bash
# CRA 多环境配置
# .env # 所有环境
# .env.local # 本地覆盖(不提交 Git)
# .env.development # 开发环境(yarn start)
# .env.production # 生产环境(yarn build)
# .env.test # 测试环境(yarn test)| 前缀 | 框架 | 说明 |
|---|---|---|
VITE_ | Vite | 客户端可访问 |
REACT_APP_ | CRA | 客户端可访问 |
NEXT_PUBLIC_ | Next.js | 客户端可访问 |
NUXT_PUBLIC_ | Nuxt 3 | 客户端可访问 |
DefinePlugin
js
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://api.example.com'),
'process.env.APP_VERSION': JSON.stringify('1.0.0'),
'__DEV__': JSON.stringify(process.env.NODE_ENV === 'development'),
'process.env.FEATURE_FLAG': JSON.stringify(true)
})
]
}js
// 代码中直接使用
console.log(process.env.API_URL) // 'https://api.example.com'
console.log(__DEV__) // true / false
// 注意:DefinePlugin 是文本替换
// ❌ 错误写法
new webpack.DefinePlugin({
'process.env.API_URL': 'https://api.example.com' // 会被当作变量名
})
// ✅ 正确写法
new webpack.DefinePlugin({
'process.env.API_URL': JSON.stringify('https://api.example.com')
})EnvironmentPlugin
js
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
// 自动读取 process.env 中的变量
new webpack.EnvironmentPlugin([
'NODE_ENV',
'API_URL',
'DEBUG'
])
]
}js
// 带默认值
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
API_URL: 'http://localhost:3000',
DEBUG: false
})| 插件 | 特点 | 适用场景 |
|---|---|---|
| DefinePlugin | 手动定义任意常量 | 需要注入非环境变量的常量 |
| EnvironmentPlugin | 自动读取 process.env | 简单的环境变量传递 |
区分构建环境
js
// webpack.config.js
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production'
return {
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(argv.mode),
'process.env.API_URL': JSON.stringify(
isProduction ? 'https://api.example.com' : 'http://localhost:3000'
)
})
]
}
}json
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack --mode production",
"build:staging": "webpack --mode production --env staging"
}
}Nuxt.js / Next.js 环境变量
Nuxt.js 配置
bash
# .env(Nuxt 3)
NUXT_PUBLIC_API_URL=https://api.example.com
NUXT_API_SECRET=sk-xxxxts
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// 仅服务端可访问
apiSecret: '',
// 客户端和服务端都可访问
public: {
apiUrl: 'http://localhost:3000'
}
}
})ts
// 代码中使用
const config = useRuntimeConfig()
console.log(config.public.apiUrl) // 客户端 + 服务端
console.log(config.apiSecret) // 仅服务端| 前缀 | 访问范围 | 说明 |
|---|---|---|
NUXT_PUBLIC_ | 客户端 + 服务端 | 公开配置 |
NUXT_ | 仅服务端 | 敏感配置 |
Next.js 配置
bash
# .env.local
NEXT_PUBLIC_API_URL=https://api.example.com
API_SECRET=sk-xxxx
DATABASE_URL=postgres://localhost:5432/dbts
// 客户端组件中使用(需要 NEXT_PUBLIC_ 前缀)
console.log(process.env.NEXT_PUBLIC_API_URL) // ✅ 客户端可访问
console.log(process.env.API_SECRET) // ❌ 客户端不可访问
// 服务端组件 / API Route 中使用
console.log(process.env.API_SECRET) // ✅ 服务端可访问
console.log(process.env.DATABASE_URL) // ✅ 服务端可访问ts
// next.config.js 自定义环境变量映射
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
APP_VERSION: '1.0.0'
}
}
module.exports = nextConfig| 前缀 | 访问范围 | 说明 |
|---|---|---|
NEXT_PUBLIC_ | 客户端 + 服务端 | 公开配置 |
| 无前缀 | 仅服务端 | 敏感配置 |
Nuxt vs Next 对比
| 特性 | Nuxt 3 | Next.js |
|---|---|---|
| 公开变量前缀 | NUXT_PUBLIC_ | NEXT_PUBLIC_ |
| 配置方式 | runtimeConfig | process.env |
| 服务端专用 | runtimeConfig(无前缀) | 无前缀变量 |
| 运行时配置 | 支持 | 不支持(构建时注入) |
Nest.js 环境变量
ConfigModule 配置
bash
npm install @nestjs/configts
// app.module.ts
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: ['.env.local', `.env.${process.env.NODE_ENV}`, '.env'],
isGlobal: true
})
]
})
export class AppModule {}ts
// 使用 ConfigService
import { ConfigService } from '@nestjs/config'
@Injectable()
export class AppService {
constructor(private configService: ConfigService) {}
getApiUrl() {
return this.configService.get<string>('API_URL')
}
getDbConfig() {
return {
host: this.configService.get<string>('DB_HOST'),
port: this.configService.get<number>('DB_PORT')
}
}
}Joi 校验
bash
npm install joits
// app.module.ts
import { ConfigModule } from '@nestjs/config'
import * as Joi from 'joi'
@Module({
imports: [
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().default(3000),
API_URL: Joi.string().uri().required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().default(5432),
JWT_SECRET: Joi.string().min(16).required()
}),
validationOptions: {
abortEarly: true
}
})
]
})
export class AppModule {}ts
// 命名空间配置
ConfigModule.forRoot({
load: [
() => ({
database: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 5432
}
})
]
})
// 使用命名空间
this.configService.get<string>('database.host')Docker 环境变量管理
docker run -e
bash
# 直接通过 -e 传递环境变量
docker run -e NODE_ENV=production \ # 设置 Node.js 运行模式
-e API_URL=https://api.example.com \ # 设置 API 地址
-e DB_PASSWORD=secret \ # 设置数据库密码
my-app # 镜像名
# 从宿主机传递已有的环境变量
export API_URL=https://api.example.com # 先在宿主机设置
docker run -e API_URL my-app # -e 后只写变量名,值自动从宿主机读取
# 使用 .env 文件批量传递(文件中每行一个 KEY=VALUE)
docker run --env-file .env my-app # --env-file 读取 .env 文件docker-compose 配置
yaml
# docker-compose.yml
services:
app:
image: my-app
environment:
- NODE_ENV=production
- API_URL=https://api.example.com
- DB_HOST=db
env_file:
- .env
- .env.production
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_USER: admin
POSTGRES_PASSWORD: ${DB_PASSWORD}.env 文件优先级
text
# docker-compose 环境变量优先级(从高到低)
1. docker-compose.yml 中的 environment(显式设置)
2. Shell 环境变量(宿主机 export)
3. .env 文件(docker-compose.yml 同目录)
4. Dockerfile 中的 ENV
# .env 文件格式
# 不支持引号包裹
API_URL=https://api.example.com # ✅
API_URL="https://api.example.com" # ❌ 值会包含引号Dockerfile 中的 ENV
dockerfile
# ---- 构建时变量(ARG):仅在 docker build 阶段可用 ----
ARG NODE_ENV=production # 构建时变量,默认 production(不保留在最终镜像中)
ARG API_URL # 构建时变量,无默认值(需通过 --build-arg 传入)
# ---- 运行时变量(ENV):保留在镜像中,容器运行时可见 ----
ENV NODE_ENV=$NODE_ENV # 将 ARG 的值传递给 ENV(这样运行时也能用)
ENV API_URL=$API_URL # 同上
# 构建命令:通过 --build-arg 传入构建参数
# docker build --build-arg API_URL=https://api.example.com .| 指令 | 阶段 | 持久化 | 说明 |
|---|---|---|---|
| ARG | 构建时 | ❌ | 仅构建阶段可用 |
| ENV | 运行时 | ✅ | 写入镜像,容器运行时可用 |
Kubernetes ConfigMap / Secret
ConfigMap
yaml
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: "production"
API_URL: "https://api.example.com"
app.properties: |
app.name=my-app
app.version=1.0.0bash
# 从命令行创建
kubectl create configmap app-config \
--from-literal=NODE_ENV=production \
--from-literal=API_URL=https://api.example.com
# 从文件创建
kubectl create configmap app-config --from-file=config.yamlSecret
yaml
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
DB_PASSWORD: c2VjcmV0MTIz # base64 编码
API_KEY: c2st(e.g., xxxx) # base64 编码bash
# 创建 Secret
kubectl create secret generic app-secret \
--from-literal=DB_PASSWORD=secret123 \
--from-literal=API_KEY=sk-xxxx
# 查看(base64 编码)
kubectl get secret app-secret -o yaml
# 解码
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -dPod 中使用
yaml
# 方式一:环境变量注入
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
image: my-app
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secret
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secret
key: DB_PASSWORDyaml
# 方式二:挂载为文件
spec:
containers:
- name: app
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: app-config| 资源类型 | 数据编码 | 版本控制 | 适用场景 |
|---|---|---|---|
| ConfigMap | 明文 | 支持 | 非敏感配置 |
| Secret | Base64 | 支持 | 敏感信息(密码、密钥) |
运行时环境变量 vs 构建时环境变量
区别
| 特性 | 构建时环境变量 | 运行时环境变量 |
|---|---|---|
| 注入时机 | npm run build 时 | 应用启动时 |
| 存储位置 | 编译到产物中 | 操作系统 / 容器环境 |
| 修改后 | 需要重新构建 | 重启应用即可 |
| 安全性 | 可能暴露在产物中 | 仅服务端可见 |
| 典型工具 | Webpack DefinePlugin、Vite | dotenv、容器环境变量 |
使用场景
text
# 构建时环境变量
# 适用:前端项目公开配置
VITE_API_URL=https://api.example.com
VITE_APP_TITLE=My App
process.env.NODE_ENV=production
# 运行时环境变量
# 适用:服务端配置、容器化部署
DATABASE_URL=postgres://localhost:5432/db
JWT_SECRET=super-secret
API_KEY=sk-xxxx前端项目的困境
text
# 前端代码运行在浏览器中,无法读取服务器环境变量
# 因此需要在构建时将变量注入到代码中
# Vite 解决方案
# 1. 构建时替换 import.meta.env.VITE_* 为实际值
# 2. 所有 VITE_ 变量在构建后成为硬编码字符串
# ⚠️ 这意味着:
# 1. 修改前端环境变量必须重新构建
# 2. 不要在前端环境变量中放敏感信息运行时注入方案
ts
// 方案一:window 全局变量(index.html 注入)
// nginx 配置
// sub_filter '__API_URL__' 'https://api.example.com';
// sub_filter_once on;
// index.html
<script>
window.__ENV__ = {
API_URL: '__API_URL__'
}
</script>
// 代码中使用
const apiUrl = window.__ENV__.API_URLts
// 方案二:配置文件动态加载
// public/config.js
window.__ENV__ = {
API_URL: 'https://api.example.com',
APP_VERSION: '1.0.0'
}
// index.html
<script src="/config.js"></script>
// 部署时只需修改 config.js,无需重新构建ts
// 方案三:Docker + nginx sub_filter
// Dockerfile
FROM nginx:alpine
COPY dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
CMD envsubst '${API_URL}' < /etc/nginx/nginx.conf > /tmp/nginx.conf && mv /tmp/nginx.conf /etc/nginx/nginx.conf && nginx -g 'daemon off;'SSR 中的环境变量处理
服务端 vs 客户端
text
# SSR 项目中环境变量存在两个运行环境
# 1. 服务端(Node.js):可访问所有 process.env 变量
# 2. 客户端(浏览器):只能访问带特定前缀的变量
# 关键原则:
# 敏感变量(API 密钥、数据库密码)只在服务端使用
# 公开变量(API 地址、应用标题)需要暴露给客户端| 框架 | 服务端变量 | 客户端变量 |
|---|---|---|
| Nuxt 3 | runtimeConfig(无前缀) | runtimeConfig.public(NUXT_PUBLIC_) |
| Next.js | process.env(无前缀) | process.env(NEXT_PUBLIC_) |
| Vite SSR | process.env | import.meta.env(VITE_) |
Vite SSR 中的变量隔离
ts
// vite.config.ts
export default defineConfig({
ssr: {
// 将这些模块排除在 SSR 外部依赖,使用 Node.js 的 process.env
noExternal: ['some-package']
}
})
// 服务端代码(Node.js 环境)
const dbPassword = process.env.DB_PASSWORD // ✅ 仅服务端
const apiUrl = import.meta.env.VITE_API_URL // ✅ 服务端也能访问
// 客户端代码(浏览器环境)
const apiUrl = import.meta.env.VITE_API_URL // ✅ 客户端可访问
const dbPassword = process.env.DB_PASSWORD // ❌ 浏览器中不可用Next.js SSR 变量处理
ts
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// 构建时将变量注入客户端代码
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY
},
// 公开运行时配置(客户端可访问)
publicRuntimeConfig: {
apiUrl: process.env.NEXT_PUBLIC_API_URL
},
// 仅服务端运行时配置
serverRuntimeConfig: {
dbPassword: process.env.DB_PASSWORD
}
}
// 服务端组件中
console.log(process.env.DB_PASSWORD) // ✅
console.log(process.env.NEXT_PUBLIC_API_URL) // ✅
// 客户端组件中
console.log(process.env.NEXT_PUBLIC_API_URL) // ✅
console.log(process.env.DB_PASSWORD) // ❌ undefined环境变量注入原理
Vite 注入原理
text
# Vite 环境变量注入流程
1. 读取 .env 文件
vite 根据 --mode 参数加载对应文件
.env → .env.local → .env.[mode] → .env.[mode].local
2. 解析键值对
只识别 VITE_ 前缀的变量
将结果合并到 import.meta.env 对象
3. 编译时替换
开发模式:通过 esbuild define 替换
构建模式:通过 rollup 插件替换
4. 最终产物
import.meta.env.VITE_API_URL → "https://api.example.com"ts
// Vite 源码中的处理逻辑(简化)
// packages/vite/src/node/env.ts
function loadEnv(mode, envDir) {
const env = {}
const envFiles = [
`.env`,
`.env.local`,
`.env.${mode}`,
`.env.${mode}.local`
]
for (const file of envFiles) {
const content = fs.readFileSync(path.join(envDir, file), 'utf-8')
// 解析 KEY=VALUE 格式
parse(content, env)
}
// 只暴露 VITE_ 前缀的变量
const filtered = {}
for (const key of Object.keys(env)) {
if (key.startsWith('VITE_')) {
filtered[key] = env[key]
}
}
return filtered
}ts
// 在 vite.config.ts 中使用 loadEnv 读取环境变量
// 常见场景:根据环境变量动态配置插件、代理等
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
// loadEnv(mode, envDir) 读取 .env 文件
// 第三个参数 '' 表示加载所有变量(不限 VITE_ 前缀)
const env = loadEnv(mode, process.cwd(), '')
return {
server: {
proxy: {
'/api': {
target: env.VITE_API_URL, // 使用环境变量配置代理目标
changeOrigin: true,
}
}
}
}
})text
# Vite 替换过程
# 源代码
const url = import.meta.env.VITE_API_URL
# 开发模式(esbuild define)
const url = "http://localhost:3000"
# 生产构建(rollup plugin)
const url = "https://api.example.com"Webpack 注入原理
text
# Webpack 环境变量注入流程
1. DefinePlugin 工作原理
在编译阶段将代码中的标识符替换为指定值
2. 替换时机
模块打包时,AST 阶段进行字符串替换
3. 替换方式
不是简单的文本替换,而是 AST 级别的替换
确保替换后的代码语法正确js
// DefinePlugin 内部原理(简化)
// 原始代码
if (process.env.NODE_ENV === 'production') {
enableLogging()
}
// DefinePlugin 配置
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
// 替换后的代码
if ('production' === 'production') {
enableLogging()
}
// 进一步被压缩工具优化为
enableLogging()js
// EnvironmentPlugin 的本质
// 等价于
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
'process.env.API_URL': JSON.stringify(process.env.API_URL)
})对比总结
| 特性 | Vite | Webpack |
|---|---|---|
| 文件加载 | 内置 .env 支持 | 需要 dotenv 插件 |
| 前缀过滤 | VITE_ | 无(需手动控制) |
| 替换工具 | esbuild / rollup | DefinePlugin |
| 开发模式 | 编译时替换 | 编译时替换 |
| 构建模式 | 编译时替换 | 编译时替换 |
| 类型安全 | 内置 ImportMetaEnv | 需手动声明 |