Service Worker キャッシュ戦略ガイド|PWA の cache-first・network-first・SWR・Workbox 設計

(更新: 2026年6月22日 ) PWA Service Worker キャッシュ Workbox パフォーマンス
結論
  • PWA の Service Worker キャッシュは リソース種別ごとに戦略を分ける のが鉄則だ。
  • ハッシュ付き JS/CSS は cache-first(プリキャッシュ)、HTML シェルは network-first、記事一覧・設定 JSON は stale-while-revalidate、決済・在庫 API は network-only
  • HTTP レイヤーの ETag・Cache-Control 設計 と併用し、クリティカルレンダリングパス最適化 で初回表示を速くしたうえで、再訪問・オフライン体験を SW で担う。
  • 本番では WorkboxprecacheAndRoute + registerRoute が保守性と安全性のバランスが最良。

Service Worker と PWA キャッシュの位置づけ

Service Worker(SW) はブラウザとネットワークの間に立つプロキシ的なスクリプトだ。fetch イベントを横取りし、Cache Storage API に保存したレスポンスを返したり、ネットワーク取得後にキャッシュを更新したりできる。PWA(Progressive Web App)では、オフライン閲覧・ホーム画面追加・プッシュ通知とセットで SW が使われる。

HTTP キャッシュ(Cache-ControlETag)だけでは実現しにくいことが SW の得意分野だ。

  • オフラインでもアプリシェルを表示 — ネットワーク不通時に白画面を避ける
  • アプリ主導の更新タイミング — デプロイ直後に古い JS を配り続ける問題を制御
  • リクエスト単位のカスタムロジック — 同一 URL でも Accept ヘッダーで戦略を変える
  • Background Sync / Push — ネットワーク復帰時の再送(本記事の範囲外だが PWA 全体設計では隣接)

一方、SW は 初回訪問ではまだ登録されていない ため、LCP や FCP の改善は HTTP キャッシュと CRP 最適化 が主役になる。SW は 再訪問・オフライン・低速回線 の体感を底上げするレイヤーと捉えると設計がブレない。

SW のライフサイクル概要

Service Worker は次の 3 段階を繰り返す。

  1. install — 初回登録時。event.waitUntil 内でプリキャッシュを実行
  2. activate — 旧キャッシュの削除、clients.claim() で即座に制御開始
  3. fetch — ページからのリクエストを横取り。ここで cache-first 等を実装
1

main thread で navigator.serviceWorker.register('/sw.js') を呼ぶ

2

ブラウザが sw.js をダウンロードし install イベントを発火

3

install 内で caches.open + cache.addAll で静的アセットをプリキャッシュ

4

activate で CACHE_VERSION が変わった旧キャッシュを caches.delete

5

以降の fetch は SW が intercept。戦略に応じて Cache Storage または network へ

6

新 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 の clonefetchcache.match で得た Response は一度しか読めない。キャッシュ保存と返却を同時に行うなら必ず response.clone() する
  • HTTPS 必須 — localhost を除き SW はセキュアコンテキストでのみ動作
  • スコープ/sw.js/ 以下のみ制御。/admin/sw.js なら /admin/ 以下に限定される
Cache Storage と HTTP キャッシュの二重保存

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].cssCache First + Precacheファイル名にハッシュ。内容不変
/ /articles HTMLNetwork FirstSEO・OGP・最新リストを優先
/api/articles GETStale-While-Revalidate体感速度と鮮度のバランス
/api/cart POSTNetwork 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)の扱いを明示
Cache First を HTML に使わない理由

ナビゲーション(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 での確認手順。

  1. Application → Service Workers → Offline にチェック
  2. Network タブで (from ServiceWorker) 表示を確認
  3. 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();
  }
});
Workbox の revision とハッシュ

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)GETCache FirstprecacheAndRouteworkbox-precache
/, /blog/** navigateGETNetwork FirstNetworkFirstpages
/api/public/**GETSWRStaleWhileRevalidateapi-public-swr
/api/me/**GETNetwork FirstNetworkFirstapi-private
/api/** 書き込みPOST 等Network Onlyキャッシュしない
外部フォント CDNGETCache FirstCacheFirstfonts
/offline.htmlGETCache Onlyprecache

HTTP キャッシュとの役割分担

SW を導入しても オリジンの Cache-Control は省略しない。CDN・ブラウザ HTTP キャッシュが効いている間は SW まで到達しないケースも多い。

担当設定例
CDN / オリジン帯域削減・304Cache-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 するまで旧ページを維持 する方が安全なことが多い。

開発中の SW にハマるポイント

DevTools の Update on reloadBypass for network を理解していないと「コードを直したのに古い SW が動く」問題が続く。開発時は Application → Service Workers → Unregister、または localhostnavigator.serviceWorker.getRegistrations().then(r => r.forEach(reg => reg.unregister())) で一旦クリアする。

オフライン UX とエラーハンドリング

PWA の価値は オフラインでも使える 体験にある。最低限の UX 設計。

  1. オフラインページ/offline.html を precache。navigate 失敗時に返す
  2. 読了済みコンテンツ — 記事詳細を runtime cache に network-first で保存
  3. 書き込みキュー — オフライン時は IndexedDB に intent を保存(Background Sync は Chromium 系中心)
  4. 明示的バナー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)
FCPCRP・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 を見る。

1

Lighthouse(Clear storage オン)で Cold の FCP/LCP を記録

2

2 回目以降(Clear storage オフ)で Repeat View を記録

3

Application → Cache Storage に期待どおりのエントリがあるか確認

4

Offline チェックで navigate / API フォールバックを手動テスト

5

SW 更新(waiting)→ バナー → reload フローをステージングで検証

6

本番デプロイ後、古い 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 導入の期待値と実装が一致する。

1

[object Object]

2

[object Object]

3

[object Object]

4

[object Object]

5

[object Object]

6

[object Object]

7

[object Object]