メインコンテンツへスキップ
Code & Craft
Web制作 約6分で読めます

Service Worker で作る PWA 完全ガイド|オフライン対応とキャッシュ戦略【2026年版】

Service Worker の基礎から実装まで完全解説。キャッシュ戦略、Workbox活用、インストール可能なPWAの作り方を実践的に学べる総合ガイド。

Progressive Web App(PWA)の中核技術である Service Worker は、オフライン対応、プッシュ通知、バックグラウンド同期など、ネイティブアプリに近い体験を Web で実現するための強力な API です。2026年現在、すべての主要ブラウザでサポートされ、実務投入が標準化しています。

Service Worker とは

Service Worker は、Web ページとは別スレッドで動作する JavaScript です。ブラウザとネットワークの間に入り、リクエストを傍受・操作できます。

主な用途:

  • オフライン対応: キャッシュから表示
  • 高速化: 二度目以降のアクセスを高速化
  • プッシュ通知: バックグラウンドでの通知受信
  • バックグラウンド同期: オフライン時の操作を復帰時に送信

Service Worker の基本

登録

// main.js
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js')
      .then(reg => console.log('SW registered:', reg))
      .catch(err => console.error('SW registration failed:', err));
  });
}

Service Worker ファイル

// sw.js
const CACHE_NAME = 'my-site-v1';
const ASSETS = [
  '/',
  '/index.html',
  '/styles.css',
  '/app.js',
  '/logo.svg',
];

// インストール時: アセットをキャッシュ
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))
  );
});

// アクティベート時: 古いキャッシュを削除
self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then(keys =>
      Promise.all(
        keys.filter(key => key !== CACHE_NAME)
            .map(key => caches.delete(key))
      )
    )
  );
});

// フェッチ時: キャッシュから返す
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(res => res || fetch(event.request))
  );
});

キャッシュ戦略の5パターン

1. Cache First(キャッシュ優先)

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      return cached || fetch(event.request);
    })
  );
});

用途: 画像、CSS、フォント等の静的アセット メリット: 高速、オフライン対応 デメリット: 更新が反映されにくい

2. Network First(ネットワーク優先)

self.addEventListener('fetch', (event) => {
  event.respondWith(
    fetch(event.request)
      .then(res => {
        const clone = res.clone();
        caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
        return res;
      })
      .catch(() => caches.match(event.request))
  );
});

用途: HTML、API レスポンス等、常に最新が欲しいもの メリット: 常に最新データ デメリット: ネットワーク遅い時は体感が悪い

3. Stale While Revalidate(古くても返しつつ更新)

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      const fetchPromise = fetch(event.request).then(res => {
        caches.open(CACHE_NAME).then(cache => cache.put(event.request, res.clone()));
        return res;
      });
      return cached || fetchPromise;
    })
  );
});

用途: ブログ記事、商品一覧等 メリット: 高速 + 最終的に最新化 デメリット: 1回目は古いデータが返る可能性

4. Cache Only(キャッシュのみ)

self.addEventListener('fetch', (event) => {
  event.respondWith(caches.match(event.request));
});

用途: 完全オフライン対応、事前インストール済みアセット

5. Network Only(ネットワークのみ)

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

用途: 分析データの送信、常に最新でなければならない API

Workbox でキャッシュを効率管理

Workbox は Google 公式の Service Worker ライブラリで、キャッシュ戦略を宣言的に書けます。

import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { ExpirationPlugin } from 'workbox-expiration';

// ビルド時に生成される manifest を使ってプリキャッシュ
precacheAndRoute(self.__WB_MANIFEST);

// 画像は Cache First(30日保持)
registerRoute(
  ({ request }) => request.destination === 'image',
  new CacheFirst({
    cacheName: 'images',
    plugins: [
      new CacheableResponsePlugin({ statuses: [0, 200] }),
      new ExpirationPlugin({
        maxEntries: 60,
        maxAgeSeconds: 30 * 24 * 60 * 60,
      }),
    ],
  })
);

// HTML は Network First
registerRoute(
  ({ request }) => request.destination === 'document',
  new NetworkFirst({
    cacheName: 'pages',
    networkTimeoutSeconds: 3,
  })
);

// CSS/JS は Stale While Revalidate
registerRoute(
  ({ request }) => ['style', 'script'].includes(request.destination),
  new StaleWhileRevalidate({ cacheName: 'assets' })
);

Web App Manifest でインストール可能に

PWA としてホーム画面に追加できるようにするには、Web App Manifest が必要です。

{
  "name": "Code & Craft",
  "short_name": "C&C",
  "description": "Web制作の実践ノウハウを発信するテックブログ",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#0284c7",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

HTML で読み込み:

<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#0284c7" />

これで条件を満たしたユーザーに「インストール」のプロンプトが表示されます。

プッシュ通知

Service Worker を使って、サイトを閉じていても通知を送れます。

// sw.js
self.addEventListener('push', (event) => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      icon: '/icons/icon-192.png',
      badge: '/icons/badge-72.png',
      data: { url: data.url },
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();
  event.waitUntil(
    clients.openWindow(event.notification.data.url)
  );
});

バックグラウンド同期

オフライン時の操作を記録し、オンライン復帰時に送信します。

// main.js
navigator.serviceWorker.ready.then(reg => {
  reg.sync.register('send-message');
});

// sw.js
self.addEventListener('sync', (event) => {
  if (event.tag === 'send-message') {
    event.waitUntil(sendQueuedMessages());
  }
});

デバッグとトラブルシューティング

Chrome DevTools

Application タブ → Service Workers で以下を確認できます。

  • 登録状況
  • ライフサイクル状態
  • ログ確認
  • 手動でのアップデート

よくあるトラブル

1. Service Worker が更新されない

  • HTTPS 環境でのみ動作(localhost は例外)
  • ブラウザの強制更新(Ctrl+Shift+R)
  • self.skipWaiting()clients.claim() を使う
self.addEventListener('install', () => {
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  event.waitUntil(clients.claim());
});

2. キャッシュが肥大化する

  • ExpirationPlugin で最大サイズと有効期限を設定
  • バージョン管理で古いキャッシュを削除

3. オフライン時にエラーページを表示したい

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => caches.match('/offline.html'))
    );
  }
});

パフォーマンスへの影響

適切に実装された Service Worker は、2回目以降のページロードを50-90%高速化できます。Core Web Vitals の LCP 改善にも大きく寄与します。

まとめ

Service Worker と PWA 技術により、Web サイトは以下を実現できます。

  • ネイティブアプリ並みの体験: オフライン対応、ホーム画面追加
  • 圧倒的な高速化: キャッシュ戦略による応答速度向上
  • エンゲージメント向上: プッシュ通知で再訪促進

まずは Workbox を使って、画像と静的アセットのキャッシュから始めるのがおすすめです。段階的に対象を広げていけば、完全な PWA に進化させられます。

参考リンク

#PWA #Service Worker #パフォーマンス #Web API #オフライン対応
シェア: