Files
TrueGrowth/docs/STARTUP_CDN_FALLBACK_LESSONS.md

5.1 KiB
Raw Blame History

首屏 CDN 回退与加载自愈经验总结

更新日期2026-04-22

现象

  • 首屏 loading 会卡在 54%58% 一类中间值不再结束
  • 实际上 HTML 已经返回,部分首屏资源也已就绪,但启动完成条件仍未满足
  • 某些静态资源本应优先走 CDN结果请求落到了源站
  • 更糟时,回退链路会拼出错误的 CDN 地址,例如:

https://cdn.jsdelivr.net/npm/aitu-app@0.6.53/npm/aitu-app@0.6.53/assets/tool-windows-Cuh0TRgJ.css

这种地址必然失败,随后页面会长时间停在半启动状态。

根因

1. 路径归一化没有在所有入口统一收口

这轮最核心的问题不是“首屏 CDN 绝对链接没写上”,而是不同阶段对资源路径的理解不一致:

  • HTML 启动阶段会生成一轮 CDN 地址
  • Service Worker 回退阶段会再生成一轮 CDN 地址
  • 但两边对输入路径的清洗规则不一致

之前只处理了:

  • /aitu-app@版本/assets/...

没有完整处理:

  • /npm/aitu-app@版本/assets/...
  • npm/aitu-app@版本/assets/...
  • 完整 jsDelivr 绝对 URL

结果就是已经带版本前缀的路径,被再次拼接了一次版本前缀,形成双前缀坏链路。

2. loading 完成条件绑得太重

这轮另一个体验问题是:首屏 loading 把“应用可展示”和“更多后台资源是否全部准备完”混在了一起。

实际用户需要的是:

  • 首屏壳层可见
  • 首屏核心 JS/CSS 可执行
  • 关键初始化已完成

不需要的是:

  • 所有预热资源
  • 所有次级页面资源
  • 所有后台预取任务

如果把这些都算进首屏完成条件,进度条就会卡在中间值,看起来像“坏了”,其实是在等不该等的东西。

这轮修复里最值得沉淀的做法

1. CDN 路径标准化必须单点收口

无论资源最初来自哪里,进入“生成 CDN URL”前都必须先做同一套标准化

  • 已经是绝对 URL直接识别不重复拼接
  • npm/aitu-app@版本/ 前缀:先剥离
  • aitu-app@版本/ 前缀:先剥离
  • 最终只保留标准 assets/... 相对路径,再统一生成 CDN 地址

经验:

  • 不要在多个阶段各自“猜一次路径”
  • 不要假设上游传来的路径一定干净
  • 路径归一化不是小工具函数,而是启动链路的正确性边界

2. HTML 入口和 SW 回退要共享同一套脏输入认知

这轮修复说明一个规律:

  • 只修 HTML 入口,不够
  • 只修 Service Worker,也不够

因为真实流量里,同一个资源可能经历:

  1. HTML 内联启动器计算首选地址
  2. 浏览器发起请求
  3. SW 拦截并尝试 CDN / 源站回退

只要其中一个阶段对脏路径处理不一致,最终就还是会出现坏链路。

经验:

  • 入口负责“生成正确首选地址”
  • SW 负责“对所有输入做最终兜底清洗”
  • 两层都要有保护,不能只赌一层

3. 首屏 loading 只等首屏,不等预热

这轮现象已经证明:

  • 预热资源可以慢
  • 后台预取可以晚一点
  • 但首屏完成感不能被它们绑住

经验:

  • 首屏 loading 的完成条件要收敛到“用户已经能正常看到并开始交互”
  • 预热应该放后台推进,只影响后续体验,不阻塞当前启动闭环
  • 进度条一旦进入后半段,必须只受首屏关键依赖控制

一句话:预热是加速器,不是门禁。

4. fallback 不只是“再试一次”,而是“保证可自愈”

这次暴露出来的问题是:

  • CDN 第一次失败
  • 源站后来成功返回
  • 但页面仍停在 58%

说明 fallback 不能只关注“网络请求最终有没有成功”,还要关注:

  • 启动状态有没有被正确推进
  • 坏链路失败后,后续正确链路是否真的接管了页面
  • 必要时是否要通过一次 reload 进入干净状态

经验:

  • 回退成功后,要么显式推进启动完成
  • 要么在确认首屏资源已就绪后做一次可控自愈
  • 不能让页面停在“资源已经够了,但状态机没走完”的中间态

这轮应该固化成默认规则的检查项

  • 任何生成 CDN 地址的地方,都先走统一归一化
  • 测试用例必须覆盖脏输入,而不只是标准 assets/...
  • 首屏完成条件只包含首屏核心资源,不包含预热任务
  • 预热失败不能阻塞页面可见
  • 回退成功后要验证“页面状态”也已经恢复,而不只是“请求成功了”

推荐的测试样例

  • assets/index-xxx.js
  • /assets/index-xxx.js
  • aitu-app@0.6.53/assets/index-xxx.js
  • /aitu-app@0.6.53/assets/index-xxx.js
  • npm/aitu-app@0.6.53/assets/index-xxx.js
  • /npm/aitu-app@0.6.53/assets/index-xxx.js
  • https://cdn.jsdelivr.net/npm/aitu-app@0.6.53/assets/index-xxx.js

这些样例都应该被归一到同一个最终资源路径。

一句话总结

这轮最值钱的经验不是“把一个错 URL 修掉了”,而是明确了两条规则:

  • CDN 回退链路里,路径标准化必须统一收口
  • 首屏 loading 只能等待首屏关键资源,不能等待后台预热

只要这两条不破,后面再做 CDN 优先、回退、自愈,系统才会又快又稳。 🚦