Nuxt 4 SSR runtimeConfig:浏览器与容器内网双地址

前端工程38 阅读约 4 分钟

SSR 框架有个新手常踩的坑:「API 地址」其实有两个

  • 服务端渲染(SSR)阶段,Node 进程在容器里跑,它访问后端应该走容器内网(如 http://api:3000),快且不出公网;
  • 浏览器水合(hydration)后,客户端发的请求要走公网域名(如 https://api.jiawen.live),因为用户的浏览器进不了你的容器网络。

如果只配一个地址,必然有一端是错的。Nuxt 的 runtimeConfig 天生就是为这件事设计的。

public 与私有分区

runtimeConfig 分两块:

  • public:会被打进客户端 bundle,浏览器能读到——放公网 API 地址;
  • 顶层(私有):只在服务端可见,不会泄露到浏览器——放容器内网地址。

我的博客前端在 compose 里是这样注入的:

web:
  environment:
    # 浏览器侧走对外地址
    NUXT_PUBLIC_API_BASE: ${API_BASE_URL}            # https://api.jiawen.live
    # SSR 数据预取走容器内网
    NUXT_API_BASE_INTERNAL: http://api:3000
    NUXT_PUBLIC_STATIC_PATH: ${STATIC_PATH}
    NUXT_PUBLIC_AI_API_BASE: ${AI_API_BASE:-}

Nuxt 有个约定:环境变量名以 NUXT_PUBLIC_ 开头会自动映射到 runtimeConfig.public.*,以 NUXT_ 开头映射到私有顶层。所以上面的变量分别落到:

  • config.public.apiBase = 公网地址(浏览器用)
  • config.apiBaseInternal = 容器内网(服务端用)

在代码里选对地址

封装一个「取 base url」的辅助逻辑,按运行环境选:

export function useApiBase() {
  const config = useRuntimeConfig()
  // import.meta.server:SSR 阶段走内网;客户端走公网
  return import.meta.server ? config.apiBaseInternal : config.public.apiBase
}

import.meta.server 让同一份代码在两端各取所需:服务端预取数据时命中内网快路径,浏览器交互时命中公网域名。

为什么不把内网地址放 public

很关键的一点:容器内网地址绝不能进 public。一是它对浏览器毫无意义(http://api:3000 在用户机器上根本解析不了),二是会无谓泄露内部拓扑。私有分区保证它只活在服务端。

STATIC_PATH(静态资源 CDN 域名)和 AI_API_BASE(AI 对话服务地址)则相反,浏览器要直接用,所以放 public。其中 AI_API_BASE 还做了「留空即关闭」的设计——没配就不渲染 AI 浮窗,让这个功能可选。

runtimeConfig vs 编译期常量

也许有人问:为什么不用 .env + 编译期替换?因为 runtimeConfig运行时读取的,意味着同一个构建产物(镜像)可以在不同环境用不同地址启动,不必为每个环境重新构建。这正是「一次构建、多处运行」的 12-factor 实践,对 Docker 部署尤其重要。

小结

SSR 的「双地址」本质是「服务端视角 ≠ 浏览器视角」。Nuxt 用 runtimeConfig 的 public / 私有分区把这件事表达得很干净:公网地址进 public 给浏览器,内网地址留私有给 SSR,用 import.meta.server 在代码里选边。再配合运行时读取,一个镜像跑遍所有环境。

相关文章

评论 (4)

陈chen

app-config.js 运行时注入这招好,一次构建多环境

M

moonlight

useEffect 清理函数这块讲清楚了,之前总忘

G

Gavin

小项目我个人更倾向轻量方案,按需演进就好

蝈蝈

TS 泛型那段如果能再展开就更好了