页面隔一阵子就自己刷新,我查错了三次才找到原因
问题
我做了一个数据同步页面,能在网页上看检测数据。部署到测试环境之后,用户反馈说页面每隔一段时间就会自己整页刷新一次,“过一阵子就刷一次”。另外手动刷一次的时候,同一个接口会发两遍。
现象
先说清楚现象,免得后面绕:
- 页面隔一段时间自己整页重载一次,不是局部刷新,是那种白一下、重新渲染的白屏重载;
- 手动刷新时,同一个接口请求发两次。
怎么解决
这个查的过程挺曲折,我错了三次,记一下。
第一次怀疑:前端容器在反复重启。 日志里看到一段像是刚起来的输出(npm ci、vite ready),用户又催着刷新,我就觉得是容器 crash-loop。但让用户查了一下容器状态,重启次数是 0,启动时间稳定了 9 个小时——容器压根没重启过。第一条推断作废。
第二次怀疑:热更新监听到源码目录有文件被改,所以整页重载。 我让用户去服务端日志里 grep "page reload",抓回来什么都没有。既然服务端 Vite 从来没发起过整页重载,那这条也不成立,作废。
第三次终于对了。 逼自己回到最朴素的问题:这条请求到底是谁发起的。让用户打开浏览器的开发者工具,看 Network 里那条请求的”发起者”和调用栈。用户截图贴回来:调用栈最底下是应用的入口文件(整个应用从头重新执行),而触发这条链的源头写着 Vite 的开发客户端。
也就是说,不是我的业务代码在刷页面,是 Vite 的开发客户端让整页重新加载了。顺着这条线去翻 node_modules/vite 的客户端源码,找到这么一段:
case "vite:ws:disconnect":
console.log(`[vite] server connection lost. Polling for restart...`);
await waitForSuccessfulPing(url.href);
location.reload(); // 断连恢复后直接整页重载
真相大白:热更新走的是一条长连接(WebSocket)。用户开着页面不动,连接长时间空闲,中间那些网络设备(公司防火墙、云负载均衡、NAT 超时)会把空闲的长连接悄悄掐掉。浏览器这边一收到”断连”,等重连成功之后,Vite 客户端就直接调 location.reload() 把整页刷一遍。这就对上了”过一阵子刷一次”——周期取决于连接被掐的时间。
这里还有个插曲值得记。我之前让用户去服务端日志里 grep connection lost,用户抓不到,我一开始还把这当成正面证据。其实那句 console.log 是打在浏览器 Console 里的,根本不在服务端日志里——方向从一开始就错了。这提醒我,给出去的命令如果方向错了,“什么都没搜到”往往说明你查错了地方,而不是问题不存在。
至于”手动刷新发两次”,是另一回事:应用入口包了一层严格模式(StrictMode),它在开发模式下故意把组件挂载两遍,用来看你有没有写副作用。生产构建就不会这样了。
结论
排查”隔一阵子自己动”这类问题,光在业务代码里 grep 定时器是不够的——开发服务器、热更新客户端、反向代理的心跳,这些东西都有一堆自己的定时逻辑,源码里根本搜不到。
最快的破局点不是继续猜,而是打开浏览器的 Network 面板,看那条请求的”发起者”和调用栈。一次截图,通常比我连着推理三轮都管用。这次就是靠一张截图才从”猜”变成”看到”的。
另外补一条实战经验:如果开发服务器挂在防火墙或者云负载均衡后面,空闲的热更新长连接很容易被悄悄掐断,要么直接把热更新关掉(server.hmr: false),要么就接受它偶尔会重载。这本质上是用开发服务器跑正式环境的一个固有代价——正式环境是打包好的静态文件,没有热更新客户端,也就不会有这条长连接,更不会自己刷。