Vite Proxy、Node 代理环境变量与 Clash 分流不生效问题排查
本文记录本仓库联调 http://192.200.125.79:9097 时的实机结论。开发机:macOS + Clash Verge(系统代理开、TUN 关)+ Node 24.19.0 + pnpm dev(Vite :3333)。
一、现象
- 浏览器打开
http://localhost:3333/obd/api/sys/captchaImage有时正常。 http://localhost:3333/obd/api/sys/publicKey一直 pending / 超时。- Clash 已配规则脚本:
"IP-CIDR,192.200.125.0/24,DIRECT,no-resolve"- Clash「代理绕过」已加
192.200.125.0/24或192.200.125.79。 ~/.zshrc已配NO_PROXY=...,192.200.125.79,同一终端echo $NO_PROXY也能看到。- 给 Vite
/obd配直连http.Agent(proxyEnv: {})后立刻正常。
根因不是「Clash 规则写错了」,而是:浏览器、Clash 规则、Node/Vite 出站不是同一条代理链路。
二、实际请求链路
登录页 axios 的 VITE_OBD_BASE_URL=/obd,浏览器只打本机:
浏览器 → localhost:3333/obd/... → Vite Dev Server
(Node http.request)
→ 192.200.125.79:9097/obd/...| 角色 | 看什么 |
|---|---|
浏览器访问 localhost:3333 | macOS 系统代理 + Clash「代理绕过」(ExceptionsList) |
浏览器直接打开 http://192.200.125.79:9097/... | 同上 |
Vite 转发 /obd | 只认进程环境变量 HTTP_PROXY / NO_PROXY,以及 http.Agent。不读 Clash 规则,也不读 macOS 绕过列表 |
Clash 规则(IP-CIDR ... DIRECT)只处理 已经进到 Clash 里的包。Vite 若带着 HTTP_PROXY=http://127.0.0.1:随机端口 出站,会先打 Clash 的 HTTP 端口;端口失效(Clash 重启后端口会变)就会一直卡住,规则轮空。
有 HTTP_PROXY 且 Node 启用了环境代理:
Vite → 127.0.0.1:Clash端口 →(此时才匹配规则)→ DIRECT / 代理节点
无 HTTP_PROXY,或 Agent.proxyEnv = {}:
Vite → 192.200.125.79:9097 (根本不进 Clash,规则不执行)三、为什么 Clash 配了还是不生效
1. 规则 ≠ 绕过 ≠ NO_PROXY
| 配置 | 作用对象 | 对本仓库 Vite /obd |
|---|---|---|
IP-CIDR,192.200.125.0/24,DIRECT | 已进入 Clash 的流量 | 不保证 Node 不进 Clash |
Clash「代理绕过」/ system_proxy_bypass | macOS ExceptionsList(浏览器) | Node 不看 |
export NO_PROXY=...192.200.125.79 | 仅当 Node 启用了环境代理时 | 见下一节 |
192.200.125.0/24 不是 192.168/10/172.16 那种私网,Clash 默认「绕过局域网」管不到。
2. Node 24 默认不看 NO_PROXY
本机实测(Node 24.19.0):
| 环境 | http.request 到 192.200.125.79 |
|---|---|
仅有 HTTP_PROXY,无 NODE_USE_ENV_PROXY | 直连(不走代理,NO_PROXY 无意义) |
NODE_USE_ENV_PROXY=1 + HTTP_PROXY + NO_PROXY 含该 IP | 直连 |
NODE_USE_ENV_PROXY=1 + HTTP_PROXY + NO_PROXY 为空 | 打到 127.0.0.1:代理端口 |
因此:只在 zshrc 里写 NO_PROXY、不设 NODE_USE_ENV_PROXY=1,对 Vite 无效。
Clash 也不会把「代理绕过」写进进程的 NO_PROXY(常见仍是 127.0.0.1,::1,localhost)。
Cursor / VS Code 从程序坞启动,不读 ~/.zshrc。必须在 即将执行 pnpm dev 的那个终端 里检查:
echo $HTTP_PROXY
echo $NO_PROXY
echo $NODE_USE_ENV_PROXY改完 zshrc 或开关 Clash 系统代理后,要 完全退出编辑器再打开,旧窗口会一直带着启动时的变量。Clash 系统代理端口还会变(如 53259 → 58389 → 10808),旧 pnpm dev 连已死端口会表现为接口一直转圈。
3. Vite agent 的坑
http-proxy-3 默认 agent = options.agent \|\| false。
| 写法 | 结果 |
|---|---|
不写 agent / agent: false | 每次新 Agent;是否走 HTTP_PROXY 取决于 Node 是否启用环境代理 |
proxyEnv: false 或 null | Node 24 会把请求卡死,不要用 |
new http.Agent({ proxyEnv: {} }) | 该 Agent 不读 HTTP_PROXY,本仓库已验证可通 |
proxyOptions: { agent: false } 不是 Vite 的合法字段,不会生效。
四、怎么确认卡在哪
- 终端(与
pnpm dev同一窗口):
env | grep -i proxy
echo $NODE_USE_ENV_PROXY- 浏览器地址栏(不要用终端 curl 代替浏览器):
http://127.0.0.1:3333/obd/api/sys/captchaImage?dt=1http://127.0.0.1:3333/obd/api/sys/publicKeyhttp://192.200.125.79:9097/obd/api/sys/publicKey
前两条走 Vite;第三条走浏览器系统代理。Vite 那条 pending、直连 9097 却很快,就是 Node 出站(HTTP_PROXY / 死端口)。两条都 pending,才是 9097 本身或网线(en13 到该网段)。
- curl 会认 macOS 系统代理,和 Node 不一致,排查时加
--noproxy '*'。
五、本仓库现状(项目内已处理)
vite.config.ts 中 /obd 强制直连,不依赖本机 NO_PROXY 是否生效:
- 启动时把
192.200.125.79写入进程NO_PROXY/no_proxy http.Agent({ keepAlive: true, timeout: 10000, proxyEnv: {} })timeout/proxyTimeout: 10000,避免无限 pending
改 vite.config.ts 后必须重启 pnpm dev。
其它访问 192.200.* 的 Node 项目,若未做同样处理,在「系统代理开着」的机器上仍可能中招。浏览器直开该 IP 只看 Clash 绕过 / TUN。
六、不改项目、只改本机
按可靠性:
1. 关系统代理 + 开 TUN(推荐)
- Clash:关「系统代理」,开 TUN
- 保留规则:
IP-CIDR,192.200.125.0/24,DIRECT,no-resolve - TUN
route-exclude-address含192.200.125.0/24(本机 Verge 已有) - 完全退出 Cursor / VS Code 再开,再
pnpm dev
系统代理一关,就不会给 Node 注入 HTTP_PROXY。
2. 启动时清掉代理(不改仓库)
env -u HTTP_PROXY -u HTTPS_PROXY -u ALL_PROXY -u http_proxy -u https_proxy -u all_proxy pnpm dev3. 让 Node 真的认 NO_PROXY(只写 NO_PROXY 不够)
~/.zshrc:
export NODE_USE_ENV_PROXY=1
export NO_PROXY="localhost,127.0.0.1,::1,192.200.125.79"
export no_proxy="$NO_PROXY"source ~/.zshrc
# 给 GUI 进程也带上(可选)
launchctl setenv NO_PROXY "localhost,127.0.0.1,::1,192.200.125.79"完全退出编辑器,新终端里确认 $NO_PROXY 含该 IP,且没被 Clash 覆盖成只有 localhost。
七、结论
| 误区 | 实际 |
|---|---|
| Clash DIRECT 了,Vite 就会直连 | Vite 可能根本没进 Clash,或卡在已死的本地代理端口 |
| 配了「代理绕过」Node 也会绕 | 那是给浏览器的;Node 只看环境变量 + Agent |
zshrc 写了 NO_PROXY 就够 | Node 24 还要 NODE_USE_ENV_PROXY=1,且编辑器要重开 |
agent: false / proxyOptions.agent | 前者不一定关掉环境代理;后者 Vite 不认 |
proxyEnv: false | 会卡死请求 |
Clash 管进入 Clash 的流量;Vite proxy 是 Node 自己的出站 HTTP。两套体系。 本仓库用直连 Agent 兜底;本机若关系统代理并开 TUN,其它 Node 项目也可少踩坑。
