前端緩存策略的自解方案全解析
從“甩鍋”到“兜底”,一套代碼實(shí)現(xiàn)緩存自愈,把用戶體驗(yàn)拉回 100 分
一、為什么“清緩存”成了技術(shù)圈的梗
“老師,頁(yè)面白屏了!”
“清下瀏覽器緩存試試。”
—— 這段對(duì)話每天都在各家公司重復(fù)上演。
用戶不會(huì)理解「緩存」是什么,他們只會(huì)覺(jué)得“你們網(wǎng)站又出 Bug 了”。
更尷尬的是,90% 的線上“舊代碼”問(wèn)題,確實(shí)只靠強(qiáng)制刷新就能解決。
于是前端背鍋,用戶流失,產(chǎn)品經(jīng)理發(fā)飆。
根源
- 靜態(tài)資源走「強(qiáng)緩存」(Cache-Control/Expires),服務(wù)器都收不到請(qǐng)求。
index.html本身也被緩存,導(dǎo)致chunk-vite-abc123.js404 卻沒(méi)人知道。- 發(fā)版窗口沒(méi)做「灰度 + 版本兜底」,一掛全掛。
目標(biāo)
讓用戶永遠(yuǎn)不再看到舊代碼,同時(shí)永遠(yuǎn)不再聽(tīng)到“清緩存”三個(gè)字。
二、先給緩存“把個(gè)脈”:瀏覽器到底緩存了誰(shuí)?
| 緩存位置 | 誰(shuí)控制 | 典型場(chǎng)景 | 是否可 JS 感知 |
|---|---|---|---|
| Memory Cache | 瀏覽器 | 同一會(huì)話后退/刷新 | ? |
| Disk Cache (HTTP 緩存) | Response Header | 強(qiáng)緩存 200(from disk) | ? |
| Service Worker Cache | 開(kāi)發(fā)者代碼 | PWA 離線包 | ? |
| Push Cache | HTTP/2 | 已廢棄 | ? |
結(jié)論
只有 Service Worker 能讓前端“自己管自己”,其余都無(wú)法在出錯(cuò)時(shí)主動(dòng)清理。
因此「讓用戶清緩存」本質(zhì)是把不可控因素甩給用戶——極不專(zhuān)業(yè)。
三、設(shè)計(jì)思路:把“發(fā)版”做成“自愈”
版本號(hào) → 可對(duì)比,每次 CI 在全局注入 __APP_VERSION__ = '1.2.3-beta.1+202509211100'
服務(wù)器 → 永遠(yuǎn)返回最新 index.html(Cache-Control: no-cache)
前端 → 輪詢(xún)版本號(hào),發(fā)現(xiàn)不一致即主動(dòng) reload并跳過(guò)緩存
兜底 → 若 JS 拋錯(cuò) 404,同樣觸發(fā) reload
灰度 → 只有帶 ?v=latest 的 5% 流量走新版本,出錯(cuò)自動(dòng)回滾
四、代碼落地(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 { defineConfig } from 'vite'
import { APP_VERSION } from './src/meta/version'
export default defineConfig({
define: {
__APP_VERSION__: JSON.stringify(APP_VERSION),
},
})
2. 版本輪詢(xún)模塊(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__) {
// 發(fā)現(xiàn)新版本
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 都是舊的,強(qiáng)制硬刷新
location.href = location.href + '?v=' + Date.now()
}
}
}
setInterval(loop, VERSION_CHECK_INTERVAL)
loop() // 立即執(zhí)行一次
}
3. 404 兜底(src/core/error-tracker.ts)
window.addEventListener('error', (e) => {
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";
}
5. 資源文件長(zhǎng)期緩存 + 內(nèi)容哈希
// vite 默認(rèn) chunk-[hash].js,確保文件名一變就 404,觸發(fā)兜底 build.rollupOptions.output.entryFileNames = 'static/js/[name]-[hash].js'
五、灰度 & 回滾:把“爆炸半徑”縮到最小
1.邊緣層(CDN/Nginx)按 Cookie 或 Query 分流
if ($arg_v = latest) { proxy_pass http://new-bucket; }
2.前端報(bào)錯(cuò)統(tǒng)一上報(bào) Sentry,1 分鐘錯(cuò)誤率 > 0.2% 自動(dòng)回滾(CI 調(diào)用 CDN 回源接口切流)
3.用戶側(cè):版本不一致時(shí)先彈柔性提示“檢測(cè)到新版本,3 秒后自動(dòng)刷新”,避免突兀。
六、最終效果
發(fā)版后 60s 內(nèi),所有在線用戶靜默切換到最新代碼。
用戶本地緩存的 chunk-abc123.js 404 → 自動(dòng)硬刷新,零人工介入。
客服再也沒(méi)收到“頁(yè)面空白”的工單。
產(chǎn)品經(jīng)理主動(dòng)在群里點(diǎn)贊:“最近怎么沒(méi)人報(bào) bug 了?”
七、常見(jiàn)疑問(wèn) Q&A
Q1. 輪詢(xún)不會(huì)增加服務(wù)器壓力嗎?
/meta.json 只有 200B,1 分鐘一次,1 萬(wàn)日活一天才 1k×60×24 ≈ 1.4M 請(qǐng)求,靜態(tài)文件 CDN 0.01 元/萬(wàn)次,成本忽略不計(jì)。
Q2. 移動(dòng)端后臺(tái)標(biāo)簽長(zhǎng)時(shí)間不刷新怎么辦?
監(jiān)聽(tīng) visibilitychange,切回前臺(tái)立即檢查版本;再配合 Service Worker 的 clients.claim() 可瞬間激活新代碼。
Q3. 企業(yè)內(nèi)網(wǎng)無(wú)法聯(lián)網(wǎng),怎么更新?
內(nèi)網(wǎng)場(chǎng)景建議把 index.html 做成 no-cache,發(fā)版通知用戶刷新當(dāng)前頁(yè)即可;其余資源仍走哈希緩存,平衡速度與可靠性。
八、結(jié)語(yǔ):把“清緩存”寫(xiě)進(jìn)歷史
“清緩存”本質(zhì)是把技術(shù)債轉(zhuǎn)嫁給用戶。
只要做到:
- 版本可感知
- 入口文件無(wú)緩存
- 舊資源 404 能兜底
- 灰度可回滾
到此這篇關(guān)于前端緩存策略的自解方案全解析的文章就介紹到這了,更多相關(guān)前端緩存策略?xún)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序動(dòng)態(tài)顯示項(xiàng)目倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了微信小程序動(dòng)態(tài)顯示項(xiàng)目倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
學(xué)習(xí)JavaScript鼠標(biāo)響應(yīng)事件
這篇文章主要帶領(lǐng)大家學(xué)習(xí)JavaScript鼠標(biāo)響應(yīng)事件,為大家分享了一個(gè)簡(jiǎn)單的鼠標(biāo)模擬案例,感興趣的小伙伴們可以參考一下2015-12-12
Javascript(AJAX)解析XML的代碼(兼容FIREFOX/IE)
Javascript AJAX 解析XML的代碼(兼容FIREFOX/IE)2010-07-07
JavaScript console對(duì)象與控制臺(tái)使用示例詳解
這篇文章主要介紹了JavaScript console對(duì)象與控制臺(tái)的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10
javascript正則表達(dá)配置擴(kuò)展名并實(shí)現(xiàn)驗(yàn)證
這篇文章主要介紹了javascript正則表達(dá)配置擴(kuò)展名并實(shí)現(xiàn)驗(yàn)證,文章圍繞主題展開(kāi)相關(guān)資料,具有以得參考價(jià)值,需要的小伙伴可以參考一下2022-02-02
《JavaScript高級(jí)編程》學(xué)習(xí)筆記之object和array引用類(lèi)型
本文給大家分享我的javascript高級(jí)編程學(xué)習(xí)筆記之object和array引用類(lèi)型,涉及到j(luò)avascript引用類(lèi)型相關(guān)知識(shí),對(duì)javascript引用類(lèi)型感興趣的朋友可以參考下本文2015-11-11
js window.open彈出新的網(wǎng)頁(yè)窗口
彈出新的網(wǎng)頁(yè)窗口的方法有很多,在本文為大家介紹下使用js的window.open方法來(lái)實(shí)現(xiàn),需要的朋友可以了解下2014-01-01

