现象
图谱页在本地开发显示几十个节点,部署到 nginx + PM2 的服务器后变成“暂无节点数据”。构建全绿,public/graph-data.json 确实存在且内容正常,浏览器 Network 里却完全看不到这个文件的请求——因为读取发生在服务端,失败点藏在 SSR 里。
排查
页面 setup 里最初的写法:
const response = await fetch(new URL('/graph-data.json', Astro.url).toString());
这是让 Node 进程向自己的站点发起 HTTP 请求。链路上任何一环都可能让它失败:nginx 对 HTTP 一律 301 到 HTTPS 后回环证书校验不过、宿主网络策略禁止进程 self-connect、上游代理超时。而外层 catch 只有一行“把数据置空”,于是所有失败都呈现为同一种体面但致命的状态——空数据。日志里没有错误,页面上没有异常,只有内容悄悄消失。
定位手法:在 SSR 进程里手动打同一条 URL 的日志与 NODE_DEBUG=http 输出对比,再在 catch 里临时记录 error 名称,很快看到 unable to verify the first certificate——HTTPS 强制回环撞上证书链校验。
修复
改为文件系统直读,HTTP 路径仅作带超时的兜底,并且把两种“空”分开渲染:
// 候选根目录:开发态命中 public/,精简部署命中 dist/client/
const result = await readPublicJson<GraphData>('graph-data.json', Astro.url);
const unavailable = !result.ok;
UI 层:unavailable 显示“数据暂时不可用,请稍后刷新”,真空数据显示“暂无节点数据”。书签页的链接体检报告同步改造。
教训
- 永远不要让 SSR 进程通过自己的对外 URL 取本地文件。磁盘直读没有网络这一层失败面。
catch后置空是最危险的降级:它把故障渲染成合法状态,让监控、日志、用户三双眼睛同时失明。降级可以,但必须带上“这是降级”的可见信号。- 凡是“本地正常、线上空了”的数据问题,优先怀疑取数路径在部署链路里多了什么环节(协议重写、证书、代理、防火墙)。