Skip to content

发布后旧页面打不开:懒加载资源与恢复设计 ​

用户上午打开应用,下午发布新版本。已打开的页面看起来一切正常,点击一个从未访问过的页面时,导航突然停住了。

旧页面仍在引用旧版本的懒加载文件。如果发布过程已经删除它们,浏览器就找不到下一页所需的代码。这个问题需要发布端和客户端共同处理。

本文来自笔记应用实践的构建、发布脚本和路由恢复实现。这里只总结源码所体现的机制,不代表核验过线上缓存头、服务器状态或部署结果。

为什么首页正常,下一页却失败 ​

当前 PC 应用使用路由懒加载,编辑器和其他页面按需下载。它改善首屏加载,但也让一次浏览器会话跨越多个发布版本。

text
旧入口 → 已加载的首页代码
                  │ 用户稍后进入编辑页
                  ▼
             旧编辑页 chunk
                  │
          发布时删除了旧文件 → 导入失败

Vite 官方说明了这个场景,并提供 vite:preloadError 事件供应用处理动态导入失败。网络中断也可能导致类似错误,不能把每次导入失败都解释为发布版本不一致。Vite Load Error Handling

先发布资源,最后切换入口 ​

当前发布脚本采用如下顺序:

  1. 在独立临时目录构建,构建期间继续提供旧产物。
  2. 检查临时产物中是否存在有效入口文件及其 JavaScript 资源。
  3. 发布新资源,每个文件先写同目录临时文件,再通过重命名替换。
  4. 更新资源清单,最后替换 HTML 入口。
  5. 清理超过保留期、且不属于当前版本的旧资源。

关键约束是:新 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 的恢复目标。
  • [ ] 有未保存内容时可以选择留下,确认重新加载时进入原目标。
  • [ ] 断网时不会无限刷新;恢复网络后可再次尝试。
  • [ ] 构建失败或资源发布失败且入口尚未切换时,旧入口仍可读取。

如果本地页面来自真实业务应用,测试前还要替换或阻断业务请求;页面运行在本机,并不代表它调用的服务也在本机。

返回系列导读,或继续阅读持续集成与发布。

为复用而记录,为理解而整理。