Service Workerはオフライン対応のプログレッシブWebアプリの基盤です。Chrome、Firefox、Safari、Edge全てのブラウザでサポートが universal になった今、オフライン対応はプログレッシブエンハンスメントではなく、本番要件です。本ガイドでは、キャッシング戦略、同期、オフラインファーストアーキテクチャの実践的なパターンを解説します。
Service Workerのライフサイクルと基礎
Service Workerは3つの主要イベントを通じて動作します。install(インストール)、activate(有効化)、fetch(フェッチ)です。インストール時に静的アセットを事前キャッシュし、有効化時に古いキャッシュをクリーンアップします。全てのフェッチリクエストがワーカーを通過するため、レスポンスを完全に制御できます。
const CACHE_NAME = "my-app-v2";
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) =>
cache.addAll(["/", "/styles/main.css", "/scripts/app.js"])
)
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
self.skipWaiting()をインストール時に、clients.claim()を有効化時に使用することで、更新後に全ての開いているページを即座に制御下に置けます。
キャッシュ戦略の詳細比較
適切なキャッシュ戦略の選択は、リソースタイプ、更新頻度、接続要件に依存します。
| 戦略 | 最適な用途 | トレードオフ |
|---|---|---|
| Cache-first | 静的アセット、画像 | 次のSW更新まで古いコンテンツ |
| Network-first | APIレスポンス、HTML | オンライン時の速度低下 |
| Stale-while-revalidate | 混合コンテンツ | 即時読み込み+バックグラウンド更新 |
| Cache-only | 不変のハッシュ付きアセット | キャッシュ欠落時にフォールバックなし |
| Network-only | リアルタイムデータ、更新系 | オフライン非対応 |
Stale-while-revalidateは多くのコンテンツに推奨されるデフォルトの戦略です。キャッシュを即座に返し、バックグラウンドで最新コピーを取得します:
async function staleWhileRevalidate(request) {
const cache = await caches.open("dynamic-v1");
const cached = await cache.match(request);
const fetchPromise = fetch(request).then((response) => {
cache.put(request, response.clone());
return response;
});
return cached || fetchPromise;
}
同時ページロード時の競合状態を防ぐには、保留中のリクエストマップを使用して重複ネットワークフェッチを回避します。
Background SyncとPeriodic Sync
Background Sync APIを使用すると、ユーザーが安定した接続を得られるまでアクションを延期できます。オフラインフォーム送信やキューイングされたAPIミューテーションに最適です。
async function queueSubmission(data) {
const db = await openDB("offline-queue", 1);
await db.add("submissions", data);
await navigator.serviceWorker.ready.then((sw) =>
sw.sync.register("submit-data")
);
}
self.addEventListener("sync", (event) => {
if (event.tag === "submit-data") {
event.waitUntil(processQueue());
}
});
Periodic Syncはさらに進んで、ブラウザ定義の間隔でバックグラウンド更新を可能にします。ニュース記事の事前取得、天気データの更新、ソーシャルメディアフィードのリフレッシュに活用できます。ユーザーの許可が必要で、Safariでは現在サポートが限定されています。
オフラインファーストアーキテクチャ
オフラインファーストとは、オフラインをデフォルトとし、接続を拡張機能として扱うアプリケーション設計です。IndexedDBを信頼できる情報源とし、バックグラウンドでリモート同期を行います。
interface SyncQueue {
id: string;
action: "create" | "update" | "delete";
endpoint: string;
body: unknown;
createdAt: Date;
}
class OfflineStore {
private db: IDBDatabase;
async enqueue(entry: SyncQueue) {
const tx = this.db.transaction("sync-queue", "readwrite");
await tx.store.add(entry);
await navigator.serviceWorker.ready.then((sw) =>
sw.sync.register("sync-queue")
);
}
async processQueue() {
const tx = this.db.transaction("sync-queue", "readonly");
const entries = await tx.store.getAll();
for (const entry of entries) {
try {
await fetch(entry.endpoint, {
method: "POST",
body: JSON.stringify(entry.body),
});
const deleteTx = this.db.transaction("sync-queue", "readwrite");
await deleteTx.store.delete(entry.id);
} catch {
break;
}
}
}
}
競合解決戦略も重要です。last-write-winsはシンプルですが情報損失のリスクがあります。CRDTやoperational transformsは協調シナリオでユーザーの意図を保持します。RxDBやPouchDBなどのフレームワークは、自動レプリケーションを備えたオフラインファースト機能を提供します。
キャッシュ管理とテスト
キャッシュのバージョン管理は、デプロイ後に古いアセットが配信されるのを防ぎます。キャッシュ名にバージョン文字列を含め、有効化時に古いキャッシュを削除します。navigator.storage.estimate()でストレージクォータを確認し、永続ストレージをリクエストして削除を防止します:
async function requestPersistentStorage() {
if (navigator.storage && navigator.storage.persist) {
const granted = await navigator.storage.persist();
console.log(`Persistent storage: ${granted ? "granted" : "denied"}`);
}
}
テストにはChrome DevToolsのオフラインシミュレーションが便利です。Playwrightを使った自動テストでは、オフライン状態での動作を検証できます:
import { test, expect } from "@playwright/test";
test("app works offline", async ({ page, context }) => {
await page.goto("https://my-app.com");
await context.setOffline(true);
await page.reload();
await expect(page.locator("h1")).toHaveText("My App");
});
Navigation PreloadはService Workerの起動パフォーマンスを改善します。インストールハンドラで有効化し、プリロードレスポンスをネットワークファーストのフォールバックとして利用します。
Service Workerは現在、Webアプリケーションの信頼性を高める本番標準のツールです。まずはstale-while-revalidateから始め、Background Syncでミューテーションを追加し、アーキテクチャの成熟に伴ってオフラインファーストへと進化させていきましょう。結果として、ユーザーの接続状態に関わらず尊重するアプリケーションが実現します。
