Initial TrueGrowth source import
This commit is contained in:
157
docs/STARTUP_CDN_FALLBACK_LESSONS.md
Normal file
157
docs/STARTUP_CDN_FALLBACK_LESSONS.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# 首屏 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 优先、回退、自愈,系统才会又快又稳。 🚦
|
||||
Reference in New Issue
Block a user