引言
在标准浏览器里,index.html 通常不长期缓存;每次打开页面都会拿到最新的入口 HTML,从而正确引用带哈希的最新 JS/CSS。
但在微信内置浏览器(包括微信客户端里打开 H5)中,index.html 常被强缓存/本地缓存。这导致一次常见事故:
- 你部署了新版本,旧版
index.html在用户端仍被缓存; - 旧版
index.html引用的app.abc123.js、chunk.abc123.css等已在服务器上删除; - 于是请求这些旧路径 404,框架无法挂载,首屏白屏;
- 用户刷新一次后,微信才会去取到新版
index.html,页面“恢复正常”,但事故已经发生。
本文给出成因分析与多套可落地的规避方案(含 Nginx/Node/OSS+CDN/Service Worker 配置与代码),你可以据此组合落地。
问题浮现(现象复盘)
- 现象:首次打开白屏;控制台常见
ChunkLoadError、某主入口*.js/*.css404。 - 复现路径:发版 → 立即用微信打开 → 白屏;刷新一次后(或过一阵)再打开就好。
- 根因:微信缓存了旧版
index.html,但静态资源走正常缓存策略,旧文件被你在服务器/CDN 侧清理了,造成入口 HTML 指向了不存在的旧资源。
误区:在
index.html里设置<meta http-equiv="Cache-Control" ...>往往不可靠;
仅靠“文件名哈希”也无法覆盖入口 HTML 自身被缓存的问题。
解决方案总览(按推荐度从高到低)
| 方案 | 核心思路 | 成本 | 风险/副作用 | 推荐度 |
|---|---|---|---|---|
| A. 为入口 URL 加“版本参数” | 让微信认为是全新地址 /?v=构建号,强制拿最新 index.html | 低 | 需服务端重写,首跳地址变化 | ⭐⭐⭐⭐⭐ |
| B. 入口文件文件名带版本 + 重写 | index-构建号.html,服务端重写到该文件 | 中 | 需要发布流程改造/软链 | ⭐⭐⭐⭐ |
| C. Service Worker 接管 HTML 缓存 | navigate 请求走 NetworkFirst | 中偏高 | 需 SW 基础与更新提示机制 | ⭐⭐⭐⭐ |
| D. 旧版本静态资源延迟清理 | 保留 N 个版本的哈希资源 | 低 | 占用存储 | ⭐⭐⭐ |
| E. HTTP/CDN 缓存策略优化 | index.html 强制 no-store,静态资源长缓存 | 低 | 在微信内可能被忽略 | ⭐⭐ |
| F. 兜底:404/ChunkLoadError 自动恢复 | 监听加载失败并一次性“带版本刷新” | 低 | 需避免循环刷新 | ⭐⭐⭐ |
实战建议:A + E + F 足以覆盖绝大多数场景;配合 D 提升容错;若团队有 PWA 能力,叠加 C 达到“可控更新”。
应急兜底方案
项目发布后,由于打包生成的 JS 文件会带有新的文件指纹(hash),而部分用户的微信浏览器仍然缓存着旧的 index.html,导致页面引用的还是旧的 JS 文件地址。当浏览器请求该 JS 资源时会返回 404,从而出现白屏。针对这种情况,我们在资源加载失败时增加了异常处理逻辑,自动触发页面刷新,重新获取最新的 index.html 和资源文件,从而恢复页面正常加载。
在main.js中添加
// // 防止微信浏览器由于缓存白屏
window.addEventListener(
'error',
(e) => {
if (
e.target &&
['link', 'script'].includes(e.target.tagName.toLowerCase())
) {
console.error('资源加载失败,自动刷新', e)
// 只刷新1次,避免死循环
if (!sessionStorage.getItem('reloaded')) {
sessionStorage.setItem('reloaded', 'true')
window.location.reload(true)
}
}
},
true
)
方案 A:为入口 URL 加“版本参数”(强烈推荐)
思路
给入口 URL(首页)加一个构建号参数:/?v=2025.08.13-01。
在服务端保证不论有没有 v 参数,始终返回相同的 index.html 内容(只是让微信误以为是新地址,从而强制请求最新 HTML)。
Nginx 配置(示例)
# 仅对 index.html 设置强制不缓存(尽力而为)
location = /index.html {
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}
# SPA 回退
location / {
try_files $uri /index.html;
}
有了上面的回退,访问
/?v=任意值也会命中/index.html。
首次进入时自动追加 v(可选)
<script>
(function() {
var BUILD_VERSION = '2025.08.13-01';
var u = new URL(window.location.href);
if (!u.searchParams.get('v')) {
u.searchParams.set('v', BUILD_VERSION);
history.replaceState(null, '', u.toString());
}
})();
</script>
CI/CD 注入构建号(示例)
BUILD_VERSION=$(date +%Y.%m.%d-%H%M)
sed -i "s/__BUILD_VERSION__/${BUILD_VERSION}/g" dist/index.html
方案 B:入口文件文件名带版本 + 重写
思路
构建产物生成 index-2025.08.13-01.html,不要覆盖旧文件;
由服务端把 / 重写到最新文件名。
Nginx(示例)
set $app_index "index-2025.08.13-01.html";
location = / {
try_files /$app_index =404;
}
location / {
try_files $uri /$app_index;
}
方案 C:用 Service Worker 接管 HTML 缓存(进阶)
self.addEventListener('install', () => self.skipWaiting());
self.addEventListener('activate', e => e.waitUntil(self.clients.claim()));
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js');
workbox.routing.registerRoute(
({request}) => request.mode === 'navigate',
new workbox.strategies.NetworkFirst({
cacheName: 'html',
networkTimeoutSeconds: 3,
})
);
workbox.routing.registerRoute(
({request}) => ['script','style','font','image'].includes(request.destination),
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'assets',
})
);
方案 D:旧版本静态资源延迟清理(容错)
保留 N 个版本的静态资源,防止旧 index.html 无法加载资源而白屏。
方案 E:HTTP/CDN 缓存策略优化(基础必做)
入口 HTML:Cache-Control: no-store, no-cache, must-revalidate, max-age=0
静态资源:Cache-Control: public, max-age=31536000, immutable

6463

被折叠的 条评论
为什么被折叠?



