# 首屏 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 优先、回退、自愈,系统才会又快又稳。 🚦