发布后旧页面打不开:懒加载资源与恢复设计
用户上午打开应用,下午发布新版本。已打开的页面看起来一切正常,点击一个从未访问过的页面时,导航突然停住了。
旧页面仍在引用旧版本的懒加载文件。如果发布过程已经删除它们,浏览器就找不到下一页所需的代码。这个问题需要发布端和客户端共同处理。
本文来自笔记应用实践的构建、发布脚本和路由恢复实现。这里只总结源码所体现的机制,不代表核验过线上缓存头、服务器状态或部署结果。
为什么首页正常,下一页却失败
当前 PC 应用使用路由懒加载,编辑器和其他页面按需下载。它改善首屏加载,但也让一次浏览器会话跨越多个发布版本。
text
旧入口 → 已加载的首页代码
│ 用户稍后进入编辑页
▼
旧编辑页 chunk
│
发布时删除了旧文件 → 导入失败Vite 官方说明了这个场景,并提供 vite:preloadError 事件供应用处理动态导入失败。网络中断也可能导致类似错误,不能把每次导入失败都解释为发布版本不一致。Vite Load Error Handling
先发布资源,最后切换入口
当前发布脚本采用如下顺序:
- 在独立临时目录构建,构建期间继续提供旧产物。
- 检查临时产物中是否存在有效入口文件及其 JavaScript 资源。
- 发布新资源,每个文件先写同目录临时文件,再通过重命名替换。
- 更新资源清单,最后替换 HTML 入口。
- 清理超过保留期、且不属于当前版本的旧资源。
关键约束是:新 HTML 出现时,它引用的资源应已可读取;旧 HTML 还在使用时,旧资源也应有过渡期。
这属于按文件发布和入口切换机制,不等于整个目录具有数据库式事务。切换入口后的进程重启或探活若失败,仍需单独处理;不能宣称任何发布失败都会自动回滚所有状态。
保留期从资源退役开始计算
假设一个版本已经稳定运行很久,某个 chunk 的修改时间也很早。发布新版时,若直接删除“修改时间早于保留期”的文件,它可能刚被替换就遭到清理。
当前脚本为上一版退出使用的资源记录退役时点,并保存当前版本的资源清单:
| 文件类型 | 处理方式 |
|---|---|
| 新版本仍引用的文件 | 无论文件时间多早,都不能清理 |
| 刚被新版本替换的文件 | 从本次退役起计算保留期 |
| 更早退役的文件 | 保留原退役时间,避免每次发布无限续期 |
| 旧清单缺失或与入口不匹配 | 保守保留现有资源,重新建立基线 |
脚本还校验清单中的路径,防止错误条目越出资源目录。清理策略应由明确清单约束,不能仅靠目录中所有文件的修改时间猜测。
有限保留期仍无法服务无限长的旧会话,所以客户端恢复入口不可省略。
恢复应该回到用户想去的页面
当前应用在 Vue Router 的错误入口识别动态导入和 CSS chunk 失败,保留失败导航的目标地址,并显示“重新加载并打开”或“留在当前页”的选择。
它没有把 Vite 官方事件示例原样当成项目实现。路由错误入口的优势是能拿到来源与目标,便于说明“重新加载以后去哪里”。
text
导航失败
│
├─ 旧导航的迟到错误 → 忽略
│
└─ 当前导航的资源错误 → 显示一次恢复提示
│
留下保存 / 确认重新加载
│
核对仍处于同一失败上下文
│
打开原目标页面如果用户先点 A,再点 B,A 的失败响应最后才到,不能把恢复目标改回 A。弹窗期间已经成功进入其他页面时,也不能在点击确认后跳回旧目标。
这与编辑会话是同一条原则:异步结果必须核对自己是否还属于当前上下文。
不要用刷新掩盖未保存内容
下面是常见但不适合编辑器的处理思路:
text
捕获加载错误 → 立即刷新
刷新后再次失败 → 再次刷新它可能丢失内存中的修改,也可能在断网时形成刷新循环。当前实现让用户明确选择,提示先保存未完成编辑,并保留浏览器的离开保护。
进一步改进可以记录一次恢复尝试的版本与目标,失败后展示离线说明或手动重试入口。但不能把这种增强写成当前已经具备的能力。
HTML 和哈希资源需要不同的缓存策略
官方建议为 HTML 配置 Cache-Control: no-cache,避免刷新后继续获得引用旧资源的入口。Vite 构建说明
no-cache 的含义是复用前需要重新验证,并非完全不存储。带内容哈希的不可变资源通常可以长期缓存,前提是同一 URL 不覆盖成另一份内容。MDN Cache-Control
实际配置还要核对 CDN、反向代理和静态服务器,源码里的 Vite 配置无法证明线上响应头正确。
对本项目的总结应写成“已经保留旧资源并提供恢复提示;部署层缓存头仍需要按环境核验”。
让用户能说清自己打开的是哪一版
当前构建会将包版本和 Git 短提交号合成界面可见的版本标识;没有 Git 信息的源码包退回包版本。
这使问题描述能从“我好像还是旧版”变成一个可比对的版本。它仍是构建时标识:要判断浏览器是否拿到新产物,必须同时核对实际返回的入口与页面所显示版本。
当前发布脚本不只检查 HTTP 成功状态,还检查返回的 HTML 是否引用本次构建入口。但这只是入口级验证,不能替代编辑、保存和恢复等业务回归。
在隔离静态环境验证
以下场景可用两套构建产物和本地静态服务器复现,不需要连接业务后端:
- [ ] 打开旧版本,切换产物后进入尚未加载的路由。
- [ ] 保留期内旧 chunk 可读取;当前清单资源不会因时间较早被删除。
- [ ] 删除某个旧 chunk 后出现恢复提示,来源页面仍保留。
- [ ] 连续导航 A、B,A 迟到的失败不会覆盖 B 的恢复目标。
- [ ] 有未保存内容时可以选择留下,确认重新加载时进入原目标。
- [ ] 断网时不会无限刷新;恢复网络后可再次尝试。
- [ ] 构建失败或资源发布失败且入口尚未切换时,旧入口仍可读取。
如果本地页面来自真实业务应用,测试前还要替换或阻断业务请求;页面运行在本机,并不代表它调用的服务也在本机。