SSR 项目中的运行时配置和环境变量如何设计?
简化版
SSR 项目要区分构建时配置和运行时配置。构建时配置会被打进产物,适合公开的静态值;运行时配置在服务启动或请求处理时读取,适合不同环境、不同租户、后端地址、灰度开关等。最重要的原则是:服务端密钥只能留在服务端,不能通过 HTML 注水或客户端 bundle 暴露给浏览器。
详细版
CSR 项目很多配置在 build 阶段就确定了,但 SSR 项目还有一个长期运行的服务进程,它可以在请求时读取环境变量、配置中心或请求上下文。
如果把所有配置都打包进前端代码,会导致环境切换困难、镜像复用能力差,也可能泄露密钥。更好的做法是把配置分层:公开配置给客户端,私密配置只在服务端使用,动态配置通过服务端接口或配置中心控制。
面试中要主动提“配置边界”:哪些能给客户端,哪些只能在 SSR server 内部使用。
完整版教学
一、先区分两类配置
构建时配置在打包时确定。
运行时配置在服务启动或请求过程中确定。
SSR 同时拥有浏览器 bundle 和服务端 bundle,所以配置泄露风险更高。
同一个变量名如果被错误注入客户端,可能直接出现在 JS 文件里。
二、配置分类
| 类型 | 示例 | 是否可暴露给客户端 |
|---|---|---|
| 公开配置 | CDN 地址、站点名称、公开 API base path | 可以 |
| 服务端配置 | 内网 API 地址、数据库代理地址 | 不应直接暴露 |
| 密钥配置 | OAuth secret、签名密钥、服务 token | 严禁暴露 |
| 动态开关 | 灰度策略、实验配置 | 看内容敏感度 |
三、为什么 SSR 更容易踩坑
SSR 代码可能同时被服务端和客户端引用。
如果把读取密钥的模块 import 到客户端组件,打包器可能把不该出现的内容带进产物。
有些框架用变量前缀区分公开变量和私密变量,例如 PUBLIC_ 之类。
命名规范和代码边界必须一起控制。
运行时配置的核心不是“能不能读环境变量”,而是“读到之后会不会被送到浏览器”。
四、代码示例
const serverConfig = {
internalApi: process.env.INTERNAL_API_URL,
oauthSecret: process.env.OAUTH_SECRET,
};
const publicConfig = {
cdnBase: process.env.PUBLIC_CDN_BASE,
appName: "Offer AIGC",
};
export function getPublicConfig() {
return publicConfig;
}
服务端渲染时只把 publicConfig 注水给客户端。
不要为了方便把整个 process.env 序列化到页面里。
五、多环境部署策略
开发、测试、预发、生产应该有明确配置来源。
容器镜像最好可复用,环境差异通过运行时变量注入。
前端静态资源域名如果是构建时确定,就需要每个环境重新构建。
SSR 服务地址、接口网关、灰度开关更适合运行时读取。
六、和缓存的关系
运行时配置如果参与页面内容,就会影响缓存 key。
按地区、租户、语言输出不同 HTML 时,CDN 缓存必须区分这些维度。
如果灰度开关影响 HTML,也要避免不同用户拿到错误版本。
配置不是孤立问题,它会传导到缓存和回滚策略。
七、误区和追问
- 误区:环境变量一定安全。 变量安全与否取决于它是否被打进客户端产物或注水到 HTML。
- 误区:SSR 里所有配置都能运行时改。 构建进 bundle 的值通常需要重新构建或重启才生效。
- 误区:公开变量前缀只是命名习惯。 在很多框架里,前缀会影响变量是否暴露到客户端。
- 追问:如何检查密钥有没有泄露? 可以扫描构建产物、HTML 注水内容和 sourcemap,同时做 secret scanning。
- 追问:配置变更需要重启吗? 环境变量通常需要重启,配置中心可做到动态刷新,但要处理一致性。
- 追问:多租户 SSR 配置怎么做? 根据 host、路径或请求头选择租户配置,并把租户维度纳入缓存设计。
八、面试回答重点
把答案落到“构建时与运行时、服务端与客户端、公开与私密、配置与缓存”四组边界。
只要边界讲清楚,说明你理解 SSR 项目不只是前端打包。