我把 “请手动清缓存” 从我的技术词典里删除了
不知道从什么时候开始,“清缓存” 成了前端和用户、产品之间心照不宣的“ 遮羞布”。
以前每逢发版,群里准会出现那几句熟悉的对白:“页面怎么白屏了?” “旧代码逻辑还在?” 而我总是习惯性地回一句:“你强制刷新一下试试。” 虽然问题解决了,但心里总觉得这事做得 不够优雅。
最近花时间复盘了前端缓存的底层逻辑,把“发版自愈”这套方案落地后,我才意识到:用户根本不该知道什么是缓存。
🌳 指望浏览器“听话”是不现实的
我以前总觉得配好了 Cache-Control 就万事大吉,但实际情况复杂得多:
入口文件被锁死: 最头疼的是
index.html。只要它被浏览器强缓存了,后续所有的chunk-[hash].js怎么变都没用,浏览器根本不会去拿新脚本。JS 抛错的“断层”: 哪怕
index.html更新了,但用户如果一直没关页面,点击跳转时去加载已经从服务器删掉的旧hash文件,直接就是一个Failed to fetch,页面瞬间卡死。
结论: 靠协议(HTTP Header)只是被动防御,靠代码(JS)才是主动出击。
🌳 让页面学会自己洗澡
1. 让代码知道“我过时了”
在打包时,我通过 Vite 注入一个全局常量 __APP_VERSION__。同时,服务器会维护一个只有几百字节的 meta.json。
页面每隔一分钟会悄悄去“对暗号”:
// 简单粗暴的轮询,但极为有效
const checkUpdate = async () => {
const { version } = await fetch(`/meta.json?t=${Date.now()}`).then((r) => r.json());
if (version !== __APP_VERSION__) {
// 发现新版本,不再等用户刷新,我们帮他刷
notifyAndReload();
}
};2. 入口文件“永不回头”
以前我为了性能给所有文件加缓存,后来在 Nginx 里,我给 index.html 判了 “死刑”:
location = /index.html {
# 彻底禁用强缓存,确保每次都是最新的暗号
add_header Cache-Control "no-cache, no-store, must-revalidate";
}3. 异常捕获的“最后一道防线”
这是最能提升体验的一点。
如果用户正在操作时,旧资源 404 了,我的代码会捕捉到这个 Runtime Error:
window.addEventListener(
'error',
(e) => {
if (isChunkLoadFailed(e)) {
// 发现是资源加载失败,很可能是发版了。
// 给 URL 带个时间戳,强制拉回最新版本
location.href = addTimestamp(location.href);
}
},
true
);🌳 避坑碎碎念
在落地这套方案时,我也踩了几个坑,记录在这里备忘:
- 轮询成本: 刚开始担心一分钟一次轮询会压垮服务器。后来一算,
meta.json只有几百字节,CDN 成本几乎为零,反倒是省下的沟通成本(解释为什么白屏)要贵得多。 - 切回前台: 很多移动端用户是切到后台再回来的。所以我加上了
visibilitychange监听,用户只要一回到页面,立刻做一次版本校验,这种“即插即用”的感觉非常丝滑。 - 灰度回滚: 现在的习惯是发版先切
5%的流量。如果Sentry监控到报错率飙升,直接在 CDN 层把index.html回滚,用户侧因为有“自感知”逻辑,会自动退回到稳健版。
🌳 总结
“清缓存” 本质上是技术债转嫁。
作为一个前端,能用代码解决的麻烦,就别去麻烦用户。现在看着后台几乎消失的 Loading Error 监控,以及产品经理再也没问过的 “为什么没更新”,这种掌控感才是写代码最爽的地方。
正如那句话:最好的 UI 体验,是用户感觉不到 UI;最好的版本更新,是用户感觉不到更新。
