“老师,页面白屏了!”
“清下浏览器缓存试试。”
—— 这段对话每天都在各家公司重复上演。
用户不会理解「缓存」是什么,他们只会觉得“你们网站又出 Bug 了”。
更尴尬的是,90% 的线上“旧代码”问题,确实只靠强制刷新就能解决。
于是前端背锅,用户流失,产品经理发飙。
根源
静态资源走「强缓存」(Cache-Control/Expires),服务器都收不到请求。
index.html 本身也被缓存,导致 chunk-vite-abc123.js 404 却没人知道。
发版窗口没做「灰度 + 版本兜底」,一挂全挂。
目标
让用户永远不再看到旧代码,同时永远不再听到“清缓存”三个字。
二、先给缓存“把个脉”:浏览器到底缓存了谁?

结论:
只有 Service Worker 能让前端“自己管自己”,其余都无法在出错时主动清理。
因此「让用户清缓存」本质是把不可控因素甩给用户——极不专业。
三、设计思路:把“发版”做成“自愈”
版本号 → 可对比每次 CI 在全局注入 __APP_VERSION__ = '1.2.3-beta.1+202509211100'
服务器 → 永远返回最新 index.html(Cache-Control: no-cache)
前端 → 轮询版本号,发现不一致即主动 reload并跳过缓存
兜底 → 若 JS 抛错 404,同样触发 reload
灰度 → 只有带 ?v=latest 的 5% 流量走新版本,出错自动回滚
四、代码落地(Vue3 + Vite 为例,React/Angular 同理)
1. CI 注入版本
# .github/workflows/release.yml
echo "export const APP_VERSION = '${GITHUB_REF_NAME}+$(date +%Y%m%d%H%M)';" > src/meta/version.js
vite.config.ts
import { APP_VERSION } from './src/meta/version'
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify(APP_VERSION),
},
})
2. 版本轮询模块(src/core/version-guard.ts)
const VERSION_CHECK_INTERVAL = 60_000 // 1min
const RETRY_MAX = 3
async function fetchMeta() {
// 加 search 防止自身被缓存
const res = await fetch('/meta.json?t=' + Date.now())
return res.json() as Promise<{ version: string }>
}
export function startVersionGuard() {
let retry = 0
const loop = async () => {
try {
const { version } = await fetchMeta()
if (version !== __APP_VERSION__) {
// 发现新版本
const event = new CustomEvent('sw-update', { detail: { version } })
window.dispatchEvent(event)
// 立即刷新,skipWaiting 效果
location.reload()
} else {
retry = 0
}
} catch (e) {
if (++retry >= RETRY_MAX && import.meta.env.PROD) {
// 可能 index.html 都是旧的,强制硬刷新
location.href = location.href + '?v=' + Date.now()
}
}
}
setInterval(loop, VERSION_CHECK_INTERVAL)
loop() // 立即执行一次
}
3. 404 兜底(src/core/error-tracker.ts)
const src = e.filename ?? ''
if (/chunk-.*\.js$/.test(src) && e.message.includes('Failed to fetch')) {
// 旧 chunk 404
sessionStorage.setItem('force-reflow', '1')
location.href = location.href + '?v=' + Date.now()
}
})
4. index.html 永不缓存
location = /index.html {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}