登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

Vite 环境变量为什么不能在运行时直接修改

来源:17golang原创

时间:2026-10-14 14:56:37 413浏览 收藏

如果你在部署后修改了服务器上的 .env,却发现浏览器里的接口地址完全没变,通常不是 Vite 读取失败,而是把构建期配置误当成了运行时配置。Vite 会在开发服务器启动或生产构建阶段读取环境变量,并把 import.meta.env 中可用的值静态替换进客户端代码;已经生成的 JavaScript 不会在浏览器运行时重新读取服务器文件。

官方地址:https://vite.dev/guide/env-and-mode.html

固定在构建产物里的值继续使用 import.meta.env;需要每次部署后都能切换的接口地址、租户标识或开关,应放在构建产物之外,由页面启动时读取。
  • 构建期值:适合版本标识、编译开关和随环境重新构建的 API 地址。
  • 运行时值:适合同一份静态资源部署到多个环境后再决定的配置。
  • 安全边界:VITE_ 变量会进入客户端代码,不能存放密钥。

先判断:你改的是构建输入,还是浏览器配置

我在排查这类问题时,先把发布链路拆成两段:Vite 运行在 Node.js 环境中,负责读取环境文件并生成静态资源;浏览器只负责加载已经生成的 HTML、JavaScript 和 CSS。两段之间的交界处就是构建产物。浏览器没有义务、也没有通道去重新解析部署服务器上的 .env.production。

因此,下面这种写法表达的是“构建时选定一个值”,不是“页面每次打开都从服务器取值”:

const apiBaseUrl = import.meta.env.VITE_API_BASE_URL

// 这个值在 Vite 启动或构建时注入,页面运行中不会自动回读 .env。
export async function loadUser() {
  const response = await fetch(`${apiBaseUrl}/user`)
  // 保留 HTTP 错误,让调用方决定如何展示失败状态。
  if (!response.ok) throw new Error(`请求失败:${response.status}`)
  return response.json()
}

开发时修改环境文件通常还需要重启开发服务器;生产环境则必须重新构建并重新部署,新的值才会进入新的 bundle。

Vite 环境文件、import.meta.env 与浏览器 bundle 的构建边界静态说明图
图1:构建期配置边界说明图,查看环境文件如何进入客户端产物。

为什么 import.meta.env 看起来像常量

Vite 官方文档把 import.meta.env 描述为一组在构建时静态替换的常量。客户端默认只暴露带有 VITE_ 前缀的自定义变量,而且暴露后的值都是字符串。这个设计让打包器可以做裁剪,也让前端代码保持简单,但代价是配置值和构建结果绑定在一起。

例如 .env.production 中写入 VITE_API_BASE_URL=https://api.example.com,构建后代码拿到的是这个字符串的产物。之后你把服务器文件改成另一个地址,除非重新执行构建,否则原 bundle 仍然携带旧值。

模式文件也属于同一个构建期边界。vite build 默认使用 production 模式;如果使用 vite build --mode staging,Vite 会按 staging 模式加载对应的环境文件。模式选择发生在构建命令中,不是页面打开后由用户切换。

const mode = import.meta.env.MODE
const isProduction = import.meta.env.PROD

// 这些字段描述当前构建模式,适合参与编译期分支或显示版本信息。
export const buildInfo = {
  mode,
  isProduction,
}

还要注意,VITE_ 只是“允许进入客户端”的前缀,不是安全存储机制。API 密钥、数据库密码和签名私钥都不应该放在这里;它们一旦进入 bundle,就可以被访问页面的人看到。

需要运行时切换时,配置要搬到启动边界之外

如果目标是“一份构建产物部署到测试、预发布和生产,再由部署动作决定接口地址”,就不要强行修改 import.meta.env。更稳妥的做法是让部署系统替换一个独立的 config.js,页面启动时先加载它,再让业务代码读取全局配置。

// public/config.js:部署时只替换这个文件,不重新构建业务 bundle。
window.__APP_CONFIG__ = {
  API_BASE_URL: "https://api.example.com"
}
// src/config.js:统一读取运行时配置,避免业务代码到处访问 window。
export function getRuntimeConfig() {
  const config = window.__APP_CONFIG__
  // 缺少配置时尽早失败,避免请求拼出错误地址。
  if (!config?.API_BASE_URL) throw new Error("缺少 API_BASE_URL")
  return config
}

生产部署时可以把 config.js 做成模板,由容器启动脚本、Web 服务器模板变量或服务端渲染入口写入实际地址。这个方案的核心不是某一种文件名,而是把“可变配置”放在编译产物之外,并明确加载顺序与缺失时的失败方式。

部署文件、config.js、window.__APP_CONFIG__ 与应用代码的运行时配置边界静态说明图
图2:运行时配置边界说明图,查看部署后可替换配置与应用读取位置。

三个容易误判的边界

第一,修改了文件但没有重启。开发服务器会在启动阶段加载环境文件,先重启再判断是否生效。生产 bundle 已经生成后,则要重新构建。

第二,把 NODE_ENV 当成 mode。Vite 文档明确区分二者:NODE_ENV 描述 Node 环境,--mode 决定 Vite 读取哪组模式文件。不要只改其中一个,就推断另一个也变了。

第三,把“能在前端读取”当成“可以保密”。所有进入 import.meta.env 的客户端值都应按公开信息处理。真正的密钥应留在后端,再由后端完成需要权限的操作。

常见问题

部署后只改 .env,为什么页面还是旧地址?

因为页面加载的是已经构建好的 JavaScript。重新构建并部署,或把该地址迁移到构建产物之外的运行时配置文件。

所有环境变量都能通过 import.meta.env 读取吗?

默认只有内置变量和带 VITE_ 前缀的变量会暴露给客户端。未加前缀的变量不会直接出现在客户端代码中;自定义前缀也可以通过 Vite 配置调整,但仍然不等于保密。

运行时配置能不能直接修改 import.meta.env?

不建议这样做。它属于 Vite 构建期提供的环境接口,不是设计给浏览器动态写入的可变对象。需要运行时变化时,建立独立的配置读取层更清楚,也更容易在缺失时给出明确错误。

判断标准可以压缩成一句话:发布前就确定的值放进 import.meta.env,发布后还要改变的值放到独立运行时配置中;任何会被浏览器读取的值,都不能当作秘密保存。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>