PWA 升級後沒更新?解決手機「獨立模式 (Standalone)」下版本更新的痛點
PWA 在手機主畫面以獨立模式運行時,常會遇到網頁程式碼已更新但使用者端卻仍卡在舊版本的窘境。本文將深入探討 Service Worker 的更新機制,並實作「主動偵測更新」與「手動重新整理」按鈕,徹底解決 PWA 的更新難題。
漸進式網頁應用程式 (PWA) 最強大的功能之一就是「安裝後像原生 App」的獨立模式 (Standalone Mode)。然而,這種模式也帶來了一個開發者最頭痛的問題:更新機制不透明且難以觸發。
在一般瀏覽器中,使用者可以隨時點擊「重新整理」按鈕。但在獨立模式下,沒有網址列,也沒有重新整理鈕。如果你的 Service Worker 設定太過強勢,使用者可能這輩子都用不到你的新功能,除非他把 App 刪掉重裝。
這篇文章將解說如何實作一套「正統」的 PWA 更新偵測機制,確保你的應用程式能像原生 App 一樣流暢更新。
為什麼 PWA 需要「偵測更新」?
PWA 的核心是 Service Worker,它像是一個位於瀏覽器與伺服器間的代理人。當你部署了新代碼,Service Worker 的行為通常如下:
- 偵測變動:瀏覽器發現伺服器上的
sw.js有變動(即使只是版本號字串不同)。 - 安裝新版:新版的 Service Worker 會被下載並進入
installing狀態。 - 進入等待期 (Waiting):為了安全,舊版的 Service Worker 會繼續控制當前頁面,直到使用者關閉所有該網站的分頁(或 App)。
問題就在這裡: 使用者通常不習慣完全關閉(Kill process)手機 App。這導致新版本永遠處於「等待中」,使用者則始終看著舊版畫面。
解決方案一:Service Worker 端的「強制接管」
第一步,我們要確保 Service Worker 一旦檢測到更新,就立即「篡位」而不要進入漫長的等待期。
在 sw.js 中實作以下邏輯:
self.addEventListener('install', (event) => {
// 讓新版 Service Worker 不需要等待舊版,安裝完立即進入 active 狀態
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
// 讓新版 Service Worker 立即取得當前所有頁面的控制權
self.clients.claim();
});
解決方案二:前端主動偵測與控制權監聽
即使 Service Worker 已經更新,當前的 React 狀態或快取的 HTML 檔案可能還是舊的。我們需要在前端實作以下三個關鍵邏輯:
1. 主動檢查更新 (reg.update())
PWA 偵測 sw.js 的頻率通常很低。我們可以在應用程式啟動時,主動請瀏覽器檢查一遍:
useEffect(() => {
if (!('serviceWorker' in navigator)) return;
navigator.serviceWorker.register('/sw.js').then(reg => {
// 每次進入頁面時,強迫檢查伺服器端的 sw.js 是否有變動
reg.update();
});
}, []);
2. 監聽控制權變更 (controllerchange)
當 Service Worker 執行了 self.clients.claim() 接管頁面時,前端會收到一個 controllerchange 事件。這是提示使用者「新版本已備妥」的最佳時機。
useEffect(() => {
const handleControllerChange = () => {
// 跳出提示問使用者是否要重新載入以使用最新版本
if (window.confirm('發現新版本,是否立即重新載入以套用更新?')) {
window.location.reload();
}
};
navigator.serviceWorker.addEventListener('controllerchange', handleControllerChange);
return () => navigator.serviceWorker.removeEventListener('controllerchange', handleControllerChange);
}, []);
解決方案三:實作手動「重新整理」按鈕
雖然有了自動偵測,但身為開發者,我們知道 window.location.reload() 有時候會失效(例如卡在 Service Worker 的強效快取)。
在 standalone 模式中,提供一個顯眼的「重新整理」按鈕是 UX 的救星:
<button onClick={() => window.location.reload()}>
<RefreshCwIcon size={16} />
<span>重新整理</span>
</button>
為什麼這有用? 當使用者點擊這個按鈕時,瀏覽器會重新發起請求。由於我們的 Service Worker 同時採用了 Network First (網路優先) 策略,這次手動重新整理能強迫瀏覽器繞過快取,去檢查伺服器上的 index.html,徹底解決「版本卡死」的問題。
總結:完美的 PWA 更新流程應具備…
- 版本號 (Cache Name):每次發佈新版,一定要在
sw.js裡更改CACHE_VERSION名稱。 - Skip Waiting:縮短 Service Worker 的換代時間。
- 智慧偵測:前端監聽
controllerchange並友善提示。 - 手動出口:永遠在 UI 某處給予使用者一個「刷新」按鈕。
透過這套「正統」方案,你的「物品期限追蹤」App 或是任何 PWA 專案,就能在不需要使用者額外開啟瀏覽器的情況下,維持在最新狀態。PWA 的開發雖然細節繁多,但只要處理好這些「系統層」的互動,它絕對能提供不輸原生應用的高品質體驗!