iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Software Development

SyncETA 開發日記:手把手打造你的智慧出發時間推薦 PWA系列 第 21 篇

【Day 21】PWA 離線功能:Service Worker 快取策略

  • 分享至 

  • xImage
  •  

第五階段開始,今天回頭把 Day 4 留下的空殼 Service Worker(public/sw.js)補完,讓 SyncETA Lite 真正具備離線能力。


快取策略的選擇

離線功能的關鍵在於「快取策略」,常見的有幾種:

  • Cache First: 先查快取,沒有才發網路請求。適合不常變動的靜態資源(如圖示、CSS)。
  • Network First: 先嘗試網路,失敗才用快取。適合像即時到站這種希望盡量新鮮、但斷網時仍能顯示「上次資料」的內容。
  • Stale While Revalidate: 先回傳快取(快),同時背景更新快取(新鮮)。

我們對靜態資源用 Cache First,對 API 資料用 Network First。


撰寫 Service Worker

// public/sw.js
const STATIC_CACHE = 'syncetalite-static-v1';
const STATIC_ASSETS = ['/', '/index.html', '/src/style.css'];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(STATIC_CACHE).then((cache) => cache.addAll(STATIC_ASSETS))
  );
});

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter((key) => key !== STATIC_CACHE).map((key) => caches.delete(key)))
    )
  );
});

self.addEventListener('fetch', (event) => {
  const { request } = event;

  // 靜態資源:Cache First
  if (STATIC_ASSETS.some((asset) => request.url.endsWith(asset))) {
    event.respondWith(
      caches.match(request).then((cached) => cached || fetch(request))
    );
    return;
  }

  // TDX / Maps API:Network First,失敗才退回快取
  if (request.url.includes('tdx.transportdata.tw') || request.url.includes('googleapis.com')) {
    event.respondWith(
      fetch(request)
        .then((response) => {
          const cloned = response.clone();
          caches.open(STATIC_CACHE).then((cache) => cache.put(request, cloned));
          return response;
        })
        .catch(() => caches.match(request))
    );
  }
});

測試離線效果

在 Chrome 開發者工具的 Network 分頁,把節流選項切換成 Offline ,重新整理頁面。如果設定正確,靜態畫面應該還能顯示,最近一次成功抓到的到站資料也會作為備援顯示,而不是整頁空白或報錯。


明天我們要加上推播通知,讓使用者不用一直盯著畫面,也能收到延誤提醒。


上一篇
【Day 20】讓使用者可以調整「想要多準時」的偏好
下一篇
【Day 22】加入推播通知(例如:延誤提醒)
系列文
SyncETA 開發日記:手把手打造你的智慧出發時間推薦 PWA 共 23 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言