直接结论:用 loadjs + 自定义 onerror 回调 + 哈希资源路径可稳住三级回源;因浏览器 script 标签无自动重试,dns 无法应对 cdn 全局故障,需前端主动捕获超时/404/证书错误并切换 cdn,配合内容哈希确保缓存一致与源站兜底。

直接结论:不用自己从零写加载器,用 loadjs + 自定义 onerror 回调 + 哈希资源路径,就能稳住三级回源(主 CDN → 备 CDN → 源站),关键在资源 URL 构造和错误捕获时机。
为什么不能只靠 DNS 或浏览器原生 fallback
CDN 故障时,https://cdn1.example.com/app.js 返回 404/503/TCP timeout,但浏览器不会自动尝试 https://cdn2.example.com/app.js —— <script src="..."></script> 没有内置重试逻辑。DNS 层的故障转移(如 Anycast 或多 IP 轮询)只解决节点级宕机,不处理整个 CDN 厂商服务中断或域名污染。真实场景中,你常会遇到:
-
net::ERR_CONNECTION_TIMED_OUT(CDN 入口网关挂了,DNS 解析成功但连不上) -
Failed to load resource: the server responded with a status of 404 ()(CDN 同步失败,资源缺失) -
net::ERR_CERT_COMMON_NAME_INVALID(CDN 证书过期或配置错,尤其多厂商混用时)
这些都得前端主动感知并切换,不能等用户刷新页面。
loadjs 是最轻量可靠的加载基座
比起手写 document.createElement('script') + onload/onerror 状态管理,loadjs 已处理好并发控制、重复加载抑制、依赖顺序、Promise 化等细节。它本身不带容灾,但暴露了 loadjs.ready() 和底层 loadjs.add(),方便注入重试逻辑。
实操建议:
- 用
loadjsv2.3+(支持onerror钩子) - 所有 JS/CSS 加载统一走
loadjs,避免混用原生标签导致状态不一致 - 不要在
onerror里直接loadjs(url2)—— 这会破坏模块依赖图,改用loadjs.add(url2, { async: true })并手动 resolve Promise - 示例片段:
loadjs('https://cdn1.example.com/app.[hash].js', 'app', { async: true }) .catch(() => { return loadjs.add('https://cdn2.example.com/app.[hash].js', { async: true }) .then(() => loadjs.ready('app')); });
资源 URL 必须带哈希指纹,否则二级加载会命中缓存脏数据
如果用 app.js 这种无版本名的路径,浏览器可能从本地缓存加载旧版 app.js,而新版已发布到 CDN2;更糟的是,CDN1 返回 404 后你切到 CDN2,但 CDN2 缓存了上一版(因未更新缓存头),结果加载了错误代码。
必须做到:
- 构建产物用内容哈希命名,例如
app.a1b2c3d4.js(Webpack 的[contenthash:8]) - HTML 中引用的永远是带哈希的 URL,不是
app.js - CDN 缓存策略设为
Cache-Control: public, immutable, max-age=31536000—— 浏览器一旦缓存就不会再发请求,所以「重试」只发生在首次加载失败时 - 源站(主域)也提供同名哈希路径,例如
https://yourdomain.com/static/app.a1b2c3d4.js,否则第三级 fallback 会 404
图片和 CSS 的 fallback 要区别对待
<img> 和 <link rel="stylesheet"> 的 onerror 行为与 <script></script> 不同:它们不阻塞渲染,也不提供 Promise 接口,且部分老浏览器不触发 onerror(如某些 iOS Safari 版本对 CORS 图片)。
稳妥做法:
- 图片用封装组件控制,比如 React 的
<safeimage src="cdn1/xxx.jpg" fallback="/static/xxx.jpg"></safeimage>,内部用useEffect+Image对象检测加载状态 - CSS 用
loadjs加载(把<link>转成动态loadjs('style.css')),统一错误处理链路 - 绝对避免在 CSS 中用
@import url("https://cdn1.example.com/xxx.css")—— 它无法捕获错误,也无法 fallback - 字体文件(
@font-face)需单独处理,建议用FontFaceAPI +document.fonts.load()检测,失败后加载备用字体 URL
真正容易被忽略的点:哈希文件名必须在 HTML 构建时就确定,不能运行时生成;CDN 切换逻辑必须在资源 URL 构造完成之后、加载发起之前介入 —— 晚了就来不及了。











