Skip to content

环境变量管理

大白话解释: 环境变量就像"不同场合的配置"。开发环境用本地 API,生产环境用线上 API,测试环境用测试 API。不用每次都改代码,切换环境变量就行。

为什么要用环境变量?

  • 安全性:密码、密钥等敏感信息不写在代码里,避免泄露
  • 灵活性:不同环境用不同的配置,不用改代码
  • 可维护性:配置集中管理,修改方便

环境变量的典型用途:

  • API 地址(开发用 localhost,生产用线上域名)
  • 调试开关(开发环境开启调试,生产环境关闭)
  • 密钥信息(数据库密码、第三方服务密钥)

.env 文件的加载顺序:

  1. .env(通用配置)
  2. .env.local(本地覆盖,不提交 Git)
  3. .env.[mode](环境特定配置)
  4. .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 内置变量

变量类型说明
MODEstring当前模式(development / production / staging)
DEVboolean是否开发环境
PRODboolean是否生产环境
SSRboolean是否服务端渲染
BASE_URLstringbase 路径,由 vite.configbase 配置决定
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.png

process.env 与 import.meta.env 的区别

特性process.envimport.meta.env
来源Node.js 全局对象Vite 注入
适用场景Node.js / SSR 端浏览器端
前缀过滤无(全部暴露)VITE_ 前缀
访问方式process.env.NODE_ENVimport.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 dotenv
js
// 入口文件顶部加载
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-expand
js
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 = 3000

cross-env 跨平台环境变量

bash
npm install cross-env --save-dev
json
{
  "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-expand

Git 配置

.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_URL

Netlify

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=secret
ts
// 代码中访问
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-xxxx
ts
// 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/db
ts
// 客户端组件中使用(需要 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 3Next.js
公开变量前缀NUXT_PUBLIC_NEXT_PUBLIC_
配置方式runtimeConfigprocess.env
服务端专用runtimeConfig(无前缀)无前缀变量
运行时配置支持不支持(构建时注入)

Nest.js 环境变量

ConfigModule 配置

bash
npm install @nestjs/config
ts
// 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 joi
ts
// 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.0
bash
# 从命令行创建
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.yaml

Secret

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 -d

Pod 中使用

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_PASSWORD
yaml
# 方式二:挂载为文件
spec:
  containers:
    - name: app
      volumeMounts:
        - name: config-volume
          mountPath: /app/config
  volumes:
    - name: config-volume
      configMap:
        name: app-config
资源类型数据编码版本控制适用场景
ConfigMap明文支持非敏感配置
SecretBase64支持敏感信息(密码、密钥)

运行时环境变量 vs 构建时环境变量

区别

特性构建时环境变量运行时环境变量
注入时机npm run build应用启动时
存储位置编译到产物中操作系统 / 容器环境
修改后需要重新构建重启应用即可
安全性可能暴露在产物中仅服务端可见
典型工具Webpack DefinePlugin、Vitedotenv、容器环境变量

使用场景

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_URL
ts
// 方案二:配置文件动态加载
// 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 3runtimeConfig(无前缀)runtimeConfig.publicNUXT_PUBLIC_
Next.jsprocess.env(无前缀)process.envNEXT_PUBLIC_
Vite SSRprocess.envimport.meta.envVITE_

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)
})

对比总结

特性ViteWebpack
文件加载内置 .env 支持需要 dotenv 插件
前缀过滤VITE_无(需手动控制)
替换工具esbuild / rollupDefinePlugin
开发模式编译时替换编译时替换
构建模式编译时替换编译时替换
类型安全内置 ImportMetaEnv需手动声明

参考

个人学习笔记,部分内容借助 AI 辅助整理,仅供查阅参考,请以官方文档为准