
AppCache 的基本原理、用途與缺陷
Application Cache(應用程式快取,簡稱 AppCache)是 HTML5 時代引入的離線快取方案。開發者可以建立一個「快取清單檔 (manifest 檔案)」,列出需離線儲存的資源。將該清單檔引用在 HTML 標籤上(例如 <html manifest="cache.appcache">),瀏覽器在有網路時會根據清單下載所有列出的檔案並快取起來。之後即使使用者斷網,瀏覽器也能用本地快取的檔案提供離線版本的網頁內容。這個機制曾被視為讓 Web 應用「脫機」的重大突破:網站的圖片、樣式、腳本甚至整個頁面都能被緩存下來,使用者下次沒有連網時依然可以瀏覽部分內容。
然而,理想很豐滿,現實卻很骨感。AppCache 雖帶來離線瀏覽的希望,但在實踐中卻暴露出許多致命缺陷:
- 更新困難:一旦網站資源被 AppCache 快取,瀏覽器即使在線也會優先載入快取內容。除非清單檔本身改變,否則使用者無法看到伺服器上的更新內容。這意味著開發者每次更新網站,都必須修改 manifest 檔(例如變更版本號)來強制用戶端重新抓取快取,否則用戶可能永遠看不到最新更新。
- 快取不靈活:AppCache 採用全有全無的策略——清單裡列出的任一資源下載失敗,整個快取更新就會失敗而回退到舊版本。這種脆弱性意味著只要其中一個檔案有問題,應用的離線快取就完全不可用。另外,除非特別在 manifest 的 NETWORK 區段中「白名單」某些路徑,否則已快取的頁面在有網路時也不會去抓取未列入快取的資源,導致部分外部資源無法載入。
- 多頁應用限制:對於多頁面網站,AppCache 的作用域讓人困惑。每個頁面都必須指向相同的 manifest 檔才能共享快取,但路徑規則複雜。一個 manifest 只能覆蓋同源下特定路徑範圍的資源,稍有設定不當就可能出現某些頁面無法離線的情況。
- 調試與錯誤處理不友善:AppCache 執行幾乎完全由瀏覽器內部黑盒子完成,開發者很難掌握發生了什麼。更新流程充滿陷阱(例如用戶必須刷新兩次才能看到新版本快取),加上 API 呆板、錯誤事件少,使得除錯和控制都極為困難。許多開發者初次嘗試 AppCache 時,都被其怪異的行為搞得焦頭爛額。
- 其他缺陷:AppCache 僅能在 HTTP/HTTPS 頁面使用,且要求 manifest 檔從與頁面相同的網域提供。儘管這些限制是為了安全考量(例如防止跨域快取漏洞),但也進一步限制了它的適用場景。
歸根究柢,AppCache 的設計過於簡單僵化,無法涵蓋真實世界 Web 應用的複雜需求。它試圖用靜態的清單規則去處理千變萬化的離線狀況,最終讓開發者挫折感倍增。難怪當年有知名前端開發者戲稱:"AppCache is a douchebag"(意即 AppCache 很坑),足見其名聲之糟糕。隨著問題層出不窮,AppCache 很快走向了被淘汰的命運。
AppCache 被淘汰的原因與歷史時間軸
由於上述缺陷,業界對 AppCache 的評價急轉直下。標準制定者很早就認識到這項技術的失敗。2016 年底,W3C 將 Application Cache 標記為廢棄技術,呼籲開發者停止使用。同時,各大瀏覽器廠商也制定了逐步淘汰 AppCache 的計劃:
- 2018 年:Chrome 等瀏覽器開始在非安全的 HTTP 網站停用 AppCache(僅允許 HTTPS 網站使用)。這一舉措在提醒開發者:離線快取必須在安全環境下進行,以保障用戶安全。
- 2019~2020 年:主流瀏覽器針對 HTTPS 情境也發出棄用警告。例如 Chrome 從 79 版(2019 年底)開始對使用 AppCache 的網站在主控台提出嚴重警告,80 版起限制了 AppCache 的作用域,以免其覆蓋整個網域。
- 2020 年 8 月:Chrome 85 版預設移除了對 AppCache 的支援(開發者即使在 HTTPS 下也無法再使用),僅透過臨時的"反向試驗機制"才能延長特定網站的 AppCache 存活期。
- 2021 年:所有主要瀏覽器全面終止了 AppCache。Chrome 95 在 2021 年 10 月完全移除了相關代碼,Firefox 85 亦在同年初取消了 AppCache 功能。至此,這項曾被寄予厚望的離線快取方案正式退出歷史舞台。
面對 AppCache 的落幕,網頁離線體驗的需求卻沒有消失。取而代之的是新的技術方案——Service Worker 驅動的 PWA。它們接過了接力棒,彌補了 AppCache 的缺陷,開啟了離線快取的全新篇章。
PWA 與 Service Worker:解決 AppCache 的終極方案
PWA(Progressive Web App,漸進式網頁應用程式)是在 2010 年代中期興起的現代 Web 應用模式,目標是讓網頁具有媲美原生 App 的體驗。其中關鍵的一環,就是可靠的離線使用能力。實現這一點的核心技術,正是 Service Worker。
Service Worker 是在瀏覽器背景執行的 JavaScript 腳本,充當網頁與網路之間的代理。它可以攔截網路請求,決定如何響應——從網路抓取最新資料,或從本地快取中提供回應,甚至在無網路時提供離線資源。簡而言之,Service Worker 賦予了 Web 開發人員對離線行為的程式化控制能力,這正是 AppCache 所缺乏的靈活性。
具體而言,Service Worker 徹底解決了 AppCache 的諸多痛點:
- 開發者可以自由實現快取策略:例如第一次訪問時透過網路抓取並快取,下次優先使用快取;或每次先嘗試網路更新內容,失敗時再用本地快取作為後援。所有這些策略都可以用程式碼清楚地表達,而不再受限於靜態清單規則。
- 更健全的更新機制:Service Worker 本身有版本概念。當開發者部署了新的 Service Worker 檔案或修改了快取邏輯,瀏覽器會在背景下載新腳本,安裝後等待激活。舊版本 Service Worker 在未被解除控制的網頁仍然運作,新版本則在適當時機接管。開發者甚至可以透過 skipWaiting() 和 clients.claim() 等 API 控制這一流程,確保用戶及時獲得更新的資源快取。相較之下,AppCache 那種「必須手動更改 manifest 觸發更新」的方法顯得非常笨拙。
- 更安全的環境:Service Worker 僅能在 HTTPS 安全協議或 localhost 下運作,確保離線快取內容不被中間人攻擊所竄改。AppCache 雖後期也要求 HTTPS,但最初在任何環境都能運行,存在一定安全隱患。而 PWA 從設計上就將安全列為前提條件。
- 更多功能:除了離線快取,Service Worker 還帶來背景同步、推送通知等能力,使 Web 應用能在背景保持部分運作,進一步縮小與原生應用的差距。這些都是 AppCache 所無法企及的領域。
憑藉 Service Worker 的強大能力,PWA 成為 AppCache 的完美接班人。自 2015 年概念提出後,PWA 技術逐漸成熟並被各大瀏覽器支援:Chrome 和 Firefox 率先實裝,Microsoft Edge 跟進,Apple 也在 2018 年的 Safari 11.1 開始支援 Service Worker。如今(2025 年),PWA 已不再是新奇事物,而是 Web 開發的重要一環。接下來,我們將通過一個簡單範例,看看如何使用 Service Worker 為網站加入離線快取功能。
使用 Service Worker 建立離線快取的範例
以下程式碼展示了一個基本的 Service Worker 腳本 sw.js,實現將網站核心資源快取至本地,並在使用者離線時提供後備頁面。請確保您的網站使用 HTTPS 並在主程式碼中正確註冊了 Service Worker(例如在主 JavaScript 檔中呼叫 navigator.serviceWorker.register('/sw.js'))。現在,來看看 sw.js 如何工作:
// 為離線快取設定一個名稱(方便版本更新時更改)
const CACHE_NAME = 'my-site-cache-v1';
// 預先定義需要快取的資源列表
const offlineResources = [
'/',
'/index.html', // 主頁 HTML
'/styles.css', // 樣式檔案
'/app.js', // 主程式碼檔案
'/offline.html' // 離線後備頁面
];
// 在 Service Worker 安裝階段觸發,快取關鍵資源
self.addEventListener('install', event => {
// 確保等待快取完成再安裝成功
event.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log('正在快取初始資源...');
return cache.addAll(offlineResources);
})
);
});
// 在 Service Worker 激活階段觸發,清理舊版本快取
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(keyList => {
// 刪除不在白名單的快取(即非當前版本的快取)
return Promise.all(keyList.map(key => {
if (key !== CACHE_NAME) {
console.log('移除過期快取:', key);
return caches.delete(key);
}
}));
})
);
});
// 攔截所有網路請求,實現離線快取策略
self.addEventListener('fetch', event => {
// 只處理同源請求,其他外部資源不干擾
if (event.request.url.startsWith(self.location.origin)) {
event.respondWith(
caches.match(event.request).then(response => {
if (response) {
// 找到對應的快取資源,直接回傳
console.log('快取命中:', event.request.url);
return response;
}
// 如無快取,嘗試從網路抓取
console.log('無快取,從網路取得:', event.request.url);
return fetch(event.request).catch(() => {
// 網路抓取失敗(可能離線),回傳預備的離線頁面
return caches.match('/offline.html');
});
})
);
}
});
上述 Service Worker 做了以下幾件事:
- 安裝階段:使用 caches.open() 打開命名的快取空間,並透過 cache.addAll() 將預先列出的重要檔案存入快取。在這個階段,只有當所有指定資源成功快取後,Service Worker 才會安裝完成(event.waitUntil 的作用)。這確保了 Service Worker 接管頁面控制前,就已備妥離線所需的基本檔案。
- 激活階段:新的 Service Worker 生效時(例如換版本更新時),觸發 activate 事件。我們在這裡遍歷所有現存的快取空間,刪除非當前版本(CACHE_NAME)的舊快取。這種做法實現了簡單的版本控制:每次我們修改 Service Worker 腳本並更換 CACHE_NAME,舊快取就會在激活時被清理,避免無用資源佔據空間或與新版本衝突。
- 攔截請求階段:這是 Service Worker 最關鍵的部分。註冊 fetch 事件可以攔截頁面發出的網路請求。在這裡我們實現了一個快取優先,網路後備的策略:對於每一個同源請求,先呼叫 caches.match() 檢查快取中是否有對應的響應。如果有就直接使用快取(快速且節省流量);如果沒有則執行 fetch(event.request) 嘗試從網路抓取。再進一步,fetch 加上 .catch() 用於處理抓取失敗的情況——當使用者離線或伺服器無響應時,我們改為從快取拿出 /offline.html 靜態頁面作為後備回應,告知使用者目前處於離線狀態。透過這樣的邏輯,即使在沒有網路時,應用也能給出體面的反饋,而非瀏覽器的錯誤頁面。
提示:我們在範例中將所有請求都套用了相同的處理策略。在實際應用中,您可以根據資源類型調整策略,例如針對 API 資料採用"網路優先"而對靜態資源採用"快取優先"。更進階的作法可以檢查 event.request.mode 或 URL 路徑來區分請求類型,分別套用不同的快取邏輯。在下節中,我們將討論多種快取策略及其取捨。
PWA 離線快取策略:Cache First、Network First 等
Service Worker 的強大之處在於可依需求制定快取策略。選擇合適的策略,能在離線體驗與即時更新之間取得平衡。以下介紹幾種常見的離線快取策略:
快取優先(Cache First)
每次請求時先查詢快取,如果有可用的快取內容就直接返回,只有當快取沒有對應項時才發起網路請求。這種策略保證了超快的響應速度與離線可用性,非常適合用於靜態資源(如 CSS、JS 檔案或圖片)等更新頻率低且體積較大的內容。缺點是如果資源在伺服器端有更新,用戶端可能一直使用舊版快取而不知情。因此常見做法是給這類資源檔名加上版本號或雜湊值,一旦內容改變就會指向新的 URL,使用者自然而然抓取新的資源並快取。
網路優先(Network First)
每次優先嘗試從網路取得最新的響應,只有在網路不可用或響應失敗時才使用本地快取作為後備。這適用於動態資訊(如 API 資料、新聞內容)等需要保持即時性的資源。採用網路優先可以確保使用者大部分時間看到的都是最新資料,同時又提供了離線狀態下的基本瀏覽能力。但缺點是每次都等待網路響應,遇上網路延遲時可能讓使用者稍感等待。此外,如果使用者第一次訪問時就離線,那因為快取還沒有資料,可能只能看到錯誤或離線頁面,因此通常需要搭配預先快取一些離線訊息或提示。
先快取再更新(Stale-While-Revalidate)
這是一種混合策略,簡而言之就是"先快速回應,再靜默更新"。當收到請求時立刻返回快取中的舊內容(保證即時響應),同時在背景發出網路請求獲取最新資源並更新快取。下一次用戶請求相同資源時就能獲得更新後的內容。這種策略兼顧了速度和新鮮度:使用者不需要等待,也最終會拿到最新資料。它適合用於頻繁更新但對實時性要求不那麼嚴苛的內容,比如一些列表資訊或用戶可能不介意延遲一點點更新的場景。在 Service Worker 中實現該策略需要多一點代碼和對 Promise 的處理,但許多現成的工具(如 Google 的 Workbox 函式庫)已提供對這類模式的封裝。
除了上述策略外,還有僅快取(Cache Only)和僅網路(Network Only)兩種極端情況:前者完全不發出網路請求(適合使用在完全本地化的資源上),後者則完全不做快取(適合某些永遠需要最新資料的端點)。在實際應用中,我們通常會針對不同類型的資源組合運用多種策略,以達到最佳效能與體驗:例如網站的離線骨架介面使用快取優先,API 數據使用網路優先或先快取再更新,第三方資源可能採用網路優先等。請記住,最優策略取決於應用場景——瞭解你的使用者最在乎什麼,是速度還是資料時效,然後相應地調整離線快取方案。
實務建議與最佳化方向
實作 PWA 離線快取時,除了選擇策略,還有一些務實的考量與最佳實踐能讓你的應用更加出色:
安全性考量
由於 Service Worker 擁有攔截所有網路請求的高權限,瀏覽器強制其必須在HTTPS 安全環境下運作(或在開發測試用的 localhost)。務必確保你的網站部署了有效的 SSL 憑證,這不僅是為了滿足技術要求,更是為了保護用戶資料傳輸安全,防止快取內容被竄改。此外,謹慎快取敏感資料:例如用戶個人資訊或私密 API 回應通常不應長期快取在客戶端,除非有適當的權限控管與加密。總之,離線快取帶來便利的同時,也意味著更多責任——請以對待生產環境程式碼同樣嚴謹的態度來撰寫 Service Worker,並定期關注瀏覽器對 PWA 安全性的最新政策變化。
效能與容量
妥善利用快取能顯著提升網站性能,但也需要留意效能最佳化的細節。首先,預先快取的資源列表不宜過大。雖然想把所有東西都存進離線包的衝動可以理解,但過多的初始快取會導致 Service Worker 安裝階段耗時過長,甚至可能因為下載量大而超時失敗。建議挑選最關鍵的資源(app shell 架構所需的 HTML、CSS、主腳本和 Logo 圖片等)進行預快取,其餘非核心資源可在首次訪問時由 fetch 事件動態快取。其次,注意控制離線快取占用的空間。不同瀏覽器對每個域名允許的離線存儲大小有所不同,但終究是有限的。如果快取策略不加節制地將每次訪問的所有內容都存下來,長期可能佔用使用者過多磁碟空間。因此應定期清理舊資源(例如我們在 activate 事件中所做的)或設定合理的快取失效策略。最後,充分利用瀏覽器開發者工具監控快取運作:例如 Chrome DevTools 的 Application 面板可查看 Cache Storage 內容及 Service Worker 狀態,善用這些工具可以調校出性能最佳的離線體驗。
版本控制與更新策略
正確的版本管理是 PWA 開發的難點之一,也是避免「更新不即時」問題的關鍵。建議每當網站前端資源有改動,就更新 Service Worker 檔案(哪怕只是改變檔案內容或名稱),以觸發瀏覽器下載新 Service Worker。配合我們在範例中展示的 CACHE_NAME 變更與 activate 邏輯,新的快取將自動接管。同時,舊版快取被清理,避免過期資源殘留。如果你的 PWA 預期會頻繁更新,考慮實作一套通知機制:當新的 Service Worker 安裝完畢並準備激活時,透過 self.skipWaiting() 加速切換,或透過 postMessage 通知前端 UI 提示使用者重新整理以套用最新版本。這樣可減少用戶持續使用舊快取的時間。另外,為了防止用戶長時間打開網頁錯過更新,你可以在網頁啟動時檢查 navigator.serviceWorker.controller 並對比快取的版本,必要時主動提示或自動刷新。良好的版本控制策略不僅確保使用者始終獲得最新內容,也讓開發者在部署新版本時更有信心離線快取不會「卡住」在舊版本。
總之,在構建離線快取時要有全局觀:從安全到效能,再到版本更新,每個環節都需要細心設計。好的離線體驗往往來自一系列細節的積累——但投資在這些細節上的時間,最終會轉化為用戶的滿意度和對你網站的信賴。
問與答
結語
從 AppCache 到 PWA,網頁離線快取技術走過了一條曲折而富有教訓的道路。我們見證了 AppCache 帶來的初曉光輝與隨後暴露的種種不足,也慶幸於 Service Worker 的出現為離線體驗帶來了真正可行的方案。如今的 Web 應用,不再害怕突然斷網時讓使用者陷入無內容可看的窘境;透過精心實作的 PWA,即使離線也能提供基本功能與內容,增強了網站的可靠性與專業度。
當然,打造優秀的離線體驗並非易事。開發者需要在快取與更新間尋找平衡,並顧及安全與效能的方方面面。然而,只要遵循本文介紹的原則與最佳實踐,循序漸進地將 PWA 能力引入你的網站,你一定能收穫頗豐。希望這篇教程能成為你踏入 PWA 世界的指引,讓更多使用者感受到現代網頁應用的魅力——不管線上線下,皆如影隨形。