Service Worker キャッシュ戦略ガイド|PWA の cache-first・network-first・SWR・Workbox 設計
- PWA の Service Worker キャッシュは リソース種別ごとに戦略を分ける のが鉄則だ。
- ハッシュ付き JS/CSS は cache-first(プリキャッシュ)、HTML シェルは network-first、記事一覧・設定 JSON は stale-while-revalidate、決済・在庫 API は network-only。
- HTTP レイヤーの ETag・Cache-Control 設計 と併用し、クリティカルレンダリングパス最適化 で初回表示を速くしたうえで、再訪問・オフライン体験を SW で担う。
- 本番では Workbox の
precacheAndRoute+registerRouteが保守性と安全性のバランスが最良。
Service Worker と PWA キャッシュの位置づけ
Service Worker(SW) はブラウザとネットワークの間に立つプロキシ的なスクリプトだ。fetch イベントを横取りし、Cache Storage API に保存したレスポンスを返したり、ネットワーク取得後にキャッシュを更新したりできる。PWA(Progressive Web App)では、オフライン閲覧・ホーム画面追加・プッシュ通知とセットで SW が使われる。
HTTP キャッシュ(Cache-Control、ETag)だけでは実現しにくいことが SW の得意分野だ。
- オフラインでもアプリシェルを表示 — ネットワーク不通時に白画面を避ける
- アプリ主導の更新タイミング — デプロイ直後に古い JS を配り続ける問題を制御
- リクエスト単位のカスタムロジック — 同一 URL でも Accept ヘッダーで戦略を変える
- Background Sync / Push — ネットワーク復帰時の再送(本記事の範囲外だが PWA 全体設計では隣接)
一方、SW は 初回訪問ではまだ登録されていない ため、LCP や FCP の改善は HTTP キャッシュと CRP 最適化 が主役になる。SW は 再訪問・オフライン・低速回線 の体感を底上げするレイヤーと捉えると設計がブレない。
SW のライフサイクル概要
Service Worker は次の 3 段階を繰り返す。
- install — 初回登録時。
event.waitUntil内でプリキャッシュを実行 - activate — 旧キャッシュの削除、
clients.claim()で即座に制御開始 - fetch — ページからのリクエストを横取り。ここで cache-first 等を実装
main thread で navigator.serviceWorker.register('/sw.js') を呼ぶ
ブラウザが sw.js をダウンロードし install イベントを発火
install 内で caches.open + cache.addAll で静的アセットをプリキャッシュ
activate で CACHE_VERSION が変わった旧キャッシュを caches.delete
以降の fetch は SW が intercept。戦略に応じて Cache Storage または network へ
新 sw.js が検出されると waiting 状態。ユーザー操作または skipWaiting で切替
Cache Storage API の基本
Service Worker が使う Cache Storage は、HTTP キャッシュとは別のキー・値ストアだ。caches.open('v1-static') で名前付きキャッシュを開き、cache.put(request, response) で Response オブジェクトを保存する。
// Cache Storage の基本操作(DevTools Console でも試せる)
async function demoCacheStorage() {
const cacheName = 'demo-v1';
const cache = await caches.open(cacheName);
const request = new Request('https://example.com/data.json');
const response = await fetch(request);
// Response は一度 read すると body が消費されるため clone が必須
await cache.put(request, response.clone());
const cached = await cache.match(request);
const data = await cached.json();
console.log('cached data:', data);
const keys = await cache.keys();
console.log('entries:', keys.map((r) => r.url));
await caches.delete(cacheName);
}
重要な制約がある。
- Response の clone —
fetchやcache.matchで得た Response は一度しか読めない。キャッシュ保存と返却を同時に行うなら必ずresponse.clone()する - HTTPS 必須 — localhost を除き SW はセキュアコンテキストでのみ動作
- スコープ —
/sw.jsは/以下のみ制御。/admin/sw.jsなら/admin/以下に限定される
SW が cache.put したレスポンスは Cache Storage に入るが、ネットワーク取得時にはブラウザの HTTP キャッシュも効く。network-first 実装では fetch(request, { cache: 'no-store' }) で HTTP キャッシュをバイパスし、SW 側だけで鮮度管理するパターンが多い。API では If-None-Match / 304 と組み合わせる場合、設計表で「どの層が鮮度を担保するか」を明文化しておく。
主要キャッシュ戦略の比較
PWA 実装で使う代表的な 4 戦略を整理する。
| 戦略 | 挙動・向き不向き |
|---|---|
| Cache First | キャッシュ → ミス時のみ network。オフライン強い。ハッシュ付き JS/CSS・フォント・ロゴ向け |
| Network First | network → 失敗時 cache。鮮度優先。HTML シェル・在庫 API・認証 JSON 向け |
| Stale-While-Revalidate | キャッシュを即返し、裏で network 更新。記事一覧・設定・サムネイル向け |
| Network Only | 常に network。キャッシュしない。決済・ログアウト・CSRF トークン向け |
| Cache Only | 常にキャッシュ。プリキャッシュ済み SPA シェル専用。API には使わない |
| 戦略 | 初回表示 | 再訪問 | オフライン | データ鮮度 |
|---|---|---|---|---|
| Cache First | 普通(network) | 最速 | 強い | 更新まで古い可能性 |
| Network First | 普通 | やや遅い | フォールバック | 高い |
| Stale-While-Revalidate | 普通 | 最速に近い | 古いデータ表示 | eventual consistency |
| Network Only | 普通 | 普通 | 不可 | 常に最新 |
リソース種別への割り当て例
| リソース | 推奨戦略 | 理由 |
|---|---|---|
app.[hash].js / style.[hash].css | Cache First + Precache | ファイル名にハッシュ。内容不変 |
/ /articles HTML | Network First | SEO・OGP・最新リストを優先 |
/api/articles GET | Stale-While-Revalidate | 体感速度と鮮度のバランス |
/api/cart POST | Network Only | キャッシュ不可 |
画像 CDN(/images/**) | CacheStorage First or SWR | 帯域節約。404 はキャッシュしない |
Cache First — 実装と適用場面
Cache First(キャッシュ優先) は、まず Cache Storage を見に行き、ヒットすれば即返す。ミスまたは undefined なら network 取得し、成功レスポンスをキャッシュに保存する。
// sw.js — Cache First(素の Service Worker 実装)
const STATIC_CACHE = 'static-v20260622';
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(STATIC_CACHE).then((cache) =>
cache.addAll([
'/',
'/offline.html',
'/manifest.webmanifest',
'/icons/icon-192.png',
'/icons/icon-512.png',
])
)
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((names) =>
Promise.all(
names
.filter((name) => name !== STATIC_CACHE)
.map((name) => caches.delete(name))
)
)
);
self.clients.claim();
});
async function cacheFirst(request) {
const cached = await caches.match(request);
if (cached) {
return cached;
}
try {
const response = await fetch(request);
if (response.ok && response.type === 'basic') {
const cache = await caches.open(STATIC_CACHE);
await cache.put(request, response.clone());
}
return response;
} catch (error) {
if (request.mode === 'navigate') {
const offline = await caches.match('/offline.html');
if (offline) return offline;
}
throw error;
}
}
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== 'GET') return;
if (
url.pathname.startsWith('/assets/') ||
url.pathname.match(/\.(js|css|woff2|png|svg|webp)$/)
) {
event.respondWith(cacheFirst(request));
}
});
Cache First の落とし穴は 古い JS が永遠に残る ことだ。対策は次のとおり。
- ビルド成果物は コンテンツハッシュ付きファイル名(
main.a1b2c3.js)にし、プリキャッシュ manifest をデプロイごとに更新 cache.addAllが 1 件でも 404 だと install 全体が reject される。存在確認してからリストに載せるresponse.type === 'basic'チェックで opaque response(CORS 外 CDN)の扱いを明示
ナビゲーション(request.mode === 'navigate')を無条件 Cache First にすると、デプロイ後もユーザーが 旧 HTML を見続ける。HTML は Network First か SWR にし、JS/CSS だけ Cache First + precache に分離する。初回表示のボトルネックは SW ではなく CRP と HTTP キャッシュ 側を先に潰す。
Network First — 鮮度優先とオフラインフォールバック
Network First は network を先に試し、成功ならキャッシュを更新して返す。タイムアウト・オフライン・5xx 時はキャッシュへフォールバックする。
// sw.js — Network First + タイムアウト
const RUNTIME_CACHE = 'runtime-v20260622';
const NETWORK_TIMEOUT_MS = 4000;
function fetchWithTimeout(request, ms) {
return Promise.race([
fetch(request),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('network timeout')), ms)
),
]);
}
async function networkFirst(request) {
try {
const response = await fetchWithTimeout(request, NETWORK_TIMEOUT_MS);
if (response.ok) {
const cache = await caches.open(RUNTIME_CACHE);
await cache.put(request, response.clone());
}
return response;
} catch (error) {
const cached = await caches.match(request);
if (cached) {
return cached;
}
if (request.mode === 'navigate') {
const offline = await caches.match('/offline.html');
if (offline) return offline;
}
return new Response(
JSON.stringify({ error: 'offline', message: 'Network unavailable' }),
{
status: 503,
headers: { 'Content-Type': 'application/json' },
}
);
}
}
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== 'GET') return;
if (url.pathname === '/' || url.pathname.startsWith('/blog/')) {
event.respondWith(networkFirst(request));
}
});
Network First が向くケース。
- HTML ドキュメント — 最新のメタ・canonical・構造化データ
- 在庫・価格 API — キャッシュヒットより正確性
- 認証後の初回ダッシュボード — 空キャッシュ時は network、以降はフォールバック
DevTools での確認手順。
- Application → Service Workers → Offline にチェック
- Network タブで
(from ServiceWorker)表示を確認 - Update on reload を ON にし、install → activate のログを Console で追う
Stale-While-Revalidate — 体感速度と鮮度の両立
Stale-While-Revalidate(SWR) は HTTP の Cache-Control: stale-while-revalidate と思想が同じだ。キャッシュがあれば 即座に返し、並行して network で取得して Cache Storage を更新する。次回以降は更新済みレスポンスが使われる。
// sw.js — Stale-While-Revalidate(素の実装)
const SWR_CACHE = 'swr-v20260622';
async function staleWhileRevalidate(request) {
const cache = await caches.open(SWR_CACHE);
const cached = await cache.match(request);
const fetchPromise = fetch(request)
.then((response) => {
if (response.ok) {
cache.put(request, response.clone());
}
return response;
})
.catch(() => null);
if (cached) {
return cached;
}
const networkResponse = await fetchPromise;
if (networkResponse) {
return networkResponse;
}
return new Response(
JSON.stringify({ error: 'offline', stale: false }),
{ status: 503, headers: { 'Content-Type': 'application/json' } }
);
}
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);
if (request.method !== 'GET') return;
if (url.pathname.startsWith('/api/') && !url.pathname.includes('/auth/')) {
event.respondWith(staleWhileRevalidate(request));
}
});
SWR 設計時のチェックリスト。
- UI 側で 「N 分前のデータ」 表示を許容するかプロダクトと合意
- POST/PUT/PATCH は SW で intercept しない(または Background Sync 専用ハンドラ)
- キャッシュキーは
request.urlだけでなく、必要ならVary相当(Accept-Language 等)を考慮 - 404 / 401 はキャッシュしない。
response.okだけでなくresponse.statusを明示判定
API キャッシュ設計 でサーバーが stale-while-revalidate=60 を返している場合、HTTP 層と SW 層の 二重 SWR にならないよう、どちらが「真の鮮度源」かを決める。
Workbox による本番構成
素の SW は学習に最適だが、ルート登録・プリキャッシュ manifest・バージョン掃除はボイラープレートが増える。Workbox(Google 製)は戦略を関数一つで宣言でき、ビルドツール連携も成熟している。
vite-plugin-pwa + Workbox の例
// vite.config.js
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
export default defineConfig({
plugins: [
VitePWA({
registerType: 'prompt',
includeAssets: ['offline.html', 'icons/*.png'],
manifest: {
name: 'Portfolio PWA',
short_name: 'Portfolio',
theme_color: '#0f172a',
background_color: '#0f172a',
display: 'standalone',
start_url: '/',
icons: [
{ src: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
],
},
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff2}'],
navigateFallback: '/offline.html',
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.example\.com\/v1\/public\/.*/i,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'api-public-swr',
expiration: {
maxEntries: 64,
maxAgeSeconds: 60 * 60 * 24,
},
cacheableResponse: {
statuses: [0, 200],
},
},
},
{
urlPattern: ({ request }) => request.mode === 'navigate',
handler: 'NetworkFirst',
options: {
cacheName: 'pages-network-first',
networkTimeoutSeconds: 4,
},
},
{
urlPattern: /\.(?:png|jpg|jpeg|svg|webp|woff2)$/,
handler: 'CacheFirst',
options: {
cacheName: 'static-media-v1',
expiration: {
maxEntries: 128,
maxAgeSeconds: 60 * 60 * 24 * 30,
},
},
},
],
},
}),
],
});
カスタム sw.js + Workbox CDN(学習・小規模向け)
// public/sw.js — Workbox 6 を importScripts で読み込む例
importScripts(
'https://storage.googleapis.com/workbox-cdn/releases/6.6.0/workbox-sw.js'
);
workbox.setConfig({ debug: false });
const { precacheAndRoute, cleanupOutdatedCaches } = workbox.precaching;
const { registerRoute } = workbox.routing;
const {
CacheFirst,
NetworkFirst,
StaleWhileRevalidate,
NetworkOnly,
} = workbox.strategies;
const { ExpirationPlugin } = workbox.expiration;
const { CacheableResponsePlugin } = workbox.cacheableResponse;
precacheAndRoute(self.__WB_MANIFEST || []);
cleanupOutdatedCaches();
registerRoute(
({ request }) => request.destination === 'document',
new NetworkFirst({
cacheName: 'html-pages',
networkTimeoutSeconds: 4,
plugins: [
new CacheableResponsePlugin({ statuses: [200] }),
new ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 }),
],
})
);
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
cacheName: 'api-swr',
plugins: [
new CacheableResponsePlugin({ statuses: [200] }),
new ExpirationPlugin({ maxEntries: 64, maxAgeSeconds: 3600 }),
],
})
);
registerRoute(
({ request }) =>
request.destination === 'script' || request.destination === 'style',
new CacheFirst({
cacheName: 'assets-cache-first',
})
);
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
precacheAndRoute はビルド時に各 URL の revision(ファイルハッシュ)を manifest に埋め込む。デプロイ後、revision が変わったファイルだけ再取得され、activate で旧エントリが削除される。手動 cache.addAll(['/app.js']) だと クエリ文字列なし同一 URL の更新検知を自前実装する必要があり、ミスりやすい。
クライアント側:登録・更新 UX・デバッグ
SW は メインスレッドから register する。HTTPS 本番と localhost 開発で挙動差があるため、feature detect を入れる。
// src/lib/registerServiceWorker.js
export async function registerServiceWorker() {
if (!('serviceWorker' in navigator)) {
console.info('[SW] Service Worker not supported');
return null;
}
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/',
updateViaCache: 'none',
});
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
if (!newWorker) return;
newWorker.addEventListener('statechange', () => {
if (
newWorker.state === 'installed' &&
navigator.serviceWorker.controller
) {
dispatchEvent(
new CustomEvent('sw:update-available', {
detail: { registration },
})
);
}
});
});
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
refreshing = true;
window.location.reload();
});
return registration;
} catch (error) {
console.error('[SW] registration failed:', error);
return null;
}
}
export function applyServiceWorkerUpdate(registration) {
const waiting = registration.waiting;
if (!waiting) return;
waiting.postMessage({ type: 'SKIP_WAITING' });
}
更新バナーの UI 例(フレームワーク非依存)。
<div id="sw-update-banner" hidden role="alert" aria-live="polite">
<p>新しいバージョンが利用可能です。</p>
<button type="button" id="sw-update-apply">更新する</button>
<button type="button" id="sw-update-dismiss">後で</button>
</div>
<script type="module">
import {
registerServiceWorker,
applyServiceWorkerUpdate,
} from '/src/lib/registerServiceWorker.js';
const banner = document.getElementById('sw-update-banner');
const applyBtn = document.getElementById('sw-update-apply');
const dismissBtn = document.getElementById('sw-update-dismiss');
let pendingRegistration = null;
window.addEventListener('sw:update-available', (event) => {
pendingRegistration = event.detail.registration;
banner.hidden = false;
});
applyBtn.addEventListener('click', () => {
if (pendingRegistration) {
applyServiceWorkerUpdate(pendingRegistration);
}
});
dismissBtn.addEventListener('click', () => {
banner.hidden = true;
});
registerServiceWorker();
</script>
戦略別ルーティング設計表
実務では URL パターン × HTTP メソッド × destination の 3 軸でルールを書く。
| パターン | メソッド | 戦略 | Workbox handler | キャッシュ名 |
|---|---|---|---|---|
| ビルド JS/CSS(precache manifest) | GET | Cache First | precacheAndRoute | workbox-precache |
/, /blog/** navigate | GET | Network First | NetworkFirst | pages |
/api/public/** | GET | SWR | StaleWhileRevalidate | api-public-swr |
/api/me/** | GET | Network First | NetworkFirst | api-private |
/api/** 書き込み | POST 等 | Network Only | — | キャッシュしない |
| 外部フォント CDN | GET | Cache First | CacheFirst | fonts |
/offline.html | GET | Cache Only | precache | — |
HTTP キャッシュとの役割分担
SW を導入しても オリジンの Cache-Control は省略しない。CDN・ブラウザ HTTP キャッシュが効いている間は SW まで到達しないケースも多い。
| 層 | 担当 | 設定例 |
|---|---|---|
| CDN / オリジン | 帯域削減・304 | Cache-Control: public, max-age=300, stale-while-revalidate=60 |
| ブラウザ HTTP | 自動キャッシュ | ETag + If-None-Match(詳細) |
| Service Worker | オフライン・ルート別制御 | cache-first / network-first / SWR |
| クライアント JS | メモリキャッシュ | React Query / SWR ライブラリ(HTTP と独立) |
クリティカルレンダリングパス最適化 で初回の JS/CSS 到着を早め、SW は 2 回目以降のナビゲーション と オフライン に効く、とチーム内で説明すると期待値調整がしやすい。
install / activate の落とし穴と対策
install が失敗する典型原因
cache.addAllの URL が 1 件でも 404 / CORS エラー- プリキャッシュリストが大きすぎて モバイルで install タイムアウト
self.skipWaiting()を install で即呼び、未保存タブが旧 SW のまま 混在
activate で旧キャッシュを確実に削除
const CACHE_VERSION = 'app-v3';
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) =>
Promise.all(
cacheNames.map((cacheName) => {
if (!cacheName.startsWith(CACHE_VERSION)) {
console.log('[SW] deleting old cache:', cacheName);
return caches.delete(cacheName);
}
return undefined;
})
)
)
);
});
clients.claim() をいつ使うか
clients.claim() は activate 直後から 既存タブ も新 SW の fetch ハンドラを通す。即時反映したいキオスク向け。一般 Web アプリでは ユーザーが reload するまで旧ページを維持 する方が安全なことが多い。
DevTools の Update on reload と Bypass for network を理解していないと「コードを直したのに古い SW が動く」問題が続く。開発時は Application → Service Workers → Unregister、または localhost で navigator.serviceWorker.getRegistrations().then(r => r.forEach(reg => reg.unregister())) で一旦クリアする。
オフライン UX とエラーハンドリング
PWA の価値は オフラインでも使える 体験にある。最低限の UX 設計。
- オフラインページ —
/offline.htmlを precache。navigate 失敗時に返す - 読了済みコンテンツ — 記事詳細を runtime cache に network-first で保存
- 書き込みキュー — オフライン時は IndexedDB に intent を保存(Background Sync は Chromium 系中心)
- 明示的バナー —
navigator.onLineと SW 503 を組み合わせ「オフライン表示中」
// オフライン検知ヘルパー(メインスレッド)
export function watchOnlineStatus(onChange) {
const update = () => onChange(navigator.onLine);
window.addEventListener('online', update);
window.addEventListener('offline', update);
update();
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
};
}
セキュリティとキャッシュに載せてはいけないもの
SW キャッシュは ディスク上に平文で残る 可能性がある。次は Network Only または no-store にする。
- 認証トークン・セッション JSON
- 個人の医療・決済情報
- 一度きりの CSRF トークン
Set-Cookieに依存するユーザー固有 HTML
CORS のない第三者 CDN リソースを cache.put すると opaque response(status 0)になり、サイズ計測もできない。Workbox では CacheableResponsePlugin で status を限定する。
計測:SW 有無での再訪問性能
SW 導入効果は 初回 vs 再訪問 を分けて計測する。
| 指標 | 初回(Cold) | 再訪問(Warm + SW) |
|---|---|---|
| FCP | CRP・HTTP が支配 | SW cache-first で JS/CSS 即返 |
| LCP | 画像 preload・CRP | 画像 runtime cache |
| TTFB(HTML) | オリジン | network-first ならオリジン往復 |
| オフライン成功率 | 0% | シェル + キャッシュ済み API |
Chrome DevTools → Lighthouse → Clear storage オフで 2 回目以降を計測。WebPageTest では Repeat View を見る。
Lighthouse(Clear storage オン)で Cold の FCP/LCP を記録
2 回目以降(Clear storage オフ)で Repeat View を記録
Application → Cache Storage に期待どおりのエントリがあるか確認
Offline チェックで navigate / API フォールバックを手動テスト
SW 更新(waiting)→ バナー → reload フローをステージングで検証
本番デプロイ後、古い CACHE_VERSION が activate で削除されたかログ確認
本番チェックリスト
デプロイ前に次を確認する。
- プリキャッシュ manifest がビルド成果物と一致(404 なし)
- HTML は network-first、ハッシュ付き asset は cache-first
- API GET と POST で戦略が分離されている
- 404 / 401 / 5xx をキャッシュしていない
- SW 更新 UI(prompt またはバナー)が実装済み
- HTTPS と
Service-Worker-Allowed(必要時)が正しい - HTTP キャッシュヘッダー と二重管理になっていない
- 初回表示は CRP 最適化 済み
関連記事
この記事は Webパフォーマンス テーマの一環です。あわせて読むと理解が深まる関連記事をまとめました。
| トピック | 記事 |
|---|---|
| HTTP/2とHTTP/3の違い | HTTP/2とHTTP/3の違い|QUICがWeb表示速度を変える理由 |
| Speculation Rules API 完全ガイド | Speculation Rules API 完全ガイド|prefetch・prerender・Chrome 144 prerender_until_script |
| fetchpriority属性でLCP画像を最適化する | fetchpriority属性でLCP画像を最適化する|2026年実践ガイド |
| Web Vitals INP改善ガイド | Web Vitals INP改善ガイド|scheduler.yield・Long Task・Input Delay対策(2026) |
| content-visibility: auto 完全ガイド | content-visibility: auto 完全ガイド|遅延レンダリングと長尺ページ最適化 2026 |
| Server-Timing API 完全ガイド | Server-Timing API 完全ガイド|Express ミドルウェアと Chrome DevTools 連携 |
| Brotli vs Gzip 圧縮比較 | Brotli vs Gzip 圧縮比較|nginx静的・動的設定とCDN実践ガイド |
| CSS/JSを圧縮(Minify)するメリット | CSS/JSを圧縮(Minify)するメリット|サイト高速化の基本 |
| View Transitions API 完全ガイド | View Transitions API 完全ガイド|SPA・MPA ページ遷移と Astro/React 統合 |
| WebP変換で画質が落ちる原因と最適な設定 | WebP変換で画質が落ちる原因と最適な設定 |
| 画像圧縮とWebP変換 | 画像圧縮とWebP変換|PageSpeed改善の基本 |
| Monorepo Turborepo Remote Cache 設計完全ガイド | Monorepo Turborepo Remote Cache 設計完全ガイド|CI パイプライン・GitHub Actions・2026 実践 |
まとめ
Service Worker キャッシュ戦略は 一つの銀弾ではなく、URL ごとのポリシー表 として設計する。cache-first で静的資産を瞬時に返し、network-first で HTML と鮮度敏感 API を守り、stale-while-revalidate で一覧・設定の体感を滑らかにする。Workbox はそのポリシーを宣言的に保ち、プリキャッシュ revision 管理でデプロイ事故を減らす。
HTTP キャッシュ(ETag、Cache-Control)と SW キャッシュは競合ではなく 積み上げ だ。初回は CRP と HTTP、再訪問とオフラインは SW——この分担をチームで共有すれば、PWA 導入の期待値と実装が一致する。
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]
[object Object]