经纬志
首页 / 实战手册 / 工程化

发布后部分用户白屏,报 ChunkLoadError

先看结论

新版本上线十分钟,客服收到反馈:一部分用户打开是白屏,清缓存后又正常。

最后验证 2026-11-03 · 多技术栈 · CDN · 更新 2026-10-29 · 引用本文

现象

控制台报懒加载分块失败,只有部分用户、部分地区出现:

Uncaught ChunkLoadError: Loading chunk 12 failed.
(missing: https://cdn.example.cn/gis/js/12.8a4f1c2e.js)
    at __webpack_require__.e (bootstrap:64)

被引用的分块文件名在 CDN 上返回 404,但服务器源站上确实存在新文件。 同一时间刷新多次,偶尔又能打开。

原因

分块文件名带 hash,每次发布会换。用户手里是旧版 index.html, 它指向旧的分块;旧分块在发布时被清理,请求就 404。 HTML 被 CDN 或浏览器缓存得越久,命中的概率越高。

多节点 CDN 回源不一致时,还会出现“这个节点有新文件、那个节点还是旧索引”的混合状态。

排查过程

  1. 看 index.html 的响应头,Cache-Control 是否为长缓存。
  2. 对比 CDN 上 HTML 引用的分块名与源站实际文件名。
  3. 用不同地区节点请求同一 HTML,比较 ETag 与内容。
curl -I https://cdn.example.cn/gis/index.html
# Cache-Control: public, max-age=86400   ← HTML 被缓存一天,问题根源

curl -s https://cdn.example.cn/gis/index.html | grep -o 'js/[0-9a-f]*\.js'

解决方案

HTML 必须不缓存或极短缓存,带 hash 的静态资源才可以长缓存:

location = /gis/index.html {
    add_header Cache-Control "no-store, no-cache, must-revalidate";
    add_header Pragma "no-cache";
}

location /gis/js/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

发布时不要立刻删除旧分块,保留一到两个版本,给已经打开页面的用户留出切换窗口:

# 构建产物按版本目录存放,软链接指向当前版本
/var/www/gis/releases/20261029/js/12.8a4f1c2e.js
/var/www/gis/releases/20261022/js/12.7b3e9d1f.js   # 保留上一版

前端加一层兜底:捕获分块加载失败后自动刷新一次,避免用户停在白屏:

window.addEventListener('unhandledrejection', function (e) {
  var msg = String(e.reason && e.reason.message || '');
  if (msg.indexOf('ChunkLoadError') > -1 || msg.indexOf('Loading chunk') > -1) {
    if (!sessionStorage.getItem('chunk-reloaded')) {
      sessionStorage.setItem('chunk-reloaded', '1');
      location.reload();
    }
  }
});

验证

index.html 响应头包含 no-store,刷新后拿到最新内容。 发布新版本后,旧页面继续操作不会 404;新打开的页面加载的是新分块。 不同地区节点请求 HTML 内容一致。

缓存策略只有一条原则:入口文件不缓存,带 hash 的资源永久缓存。 反过来配,必然出现新旧混用。

引用本文

经纬志(2026)。《发布后部分用户白屏,报 ChunkLoadError》。https://www.jingweizhi.com/cookbook/cdn-static-cache.html(访问日期 2026-11-03)。