Files
TrueGrowth/docs/STARTUP_CDN_FALLBACK_LESSONS.md

158 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 首屏 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 优先、回退、自愈,系统才会又快又稳。 🚦