Use Cases

Progressive Web App avec Claude Code

Découvrez progressive Web App avec Claude Code. Conseils pratiques et exemples de code inclus.

PWAとは

PWA(Progressive Web App)はWebの技術でネイティブアプリに近い体験を提供するアプローチです。Claude Codeを使えば、Service Workerやマニフェストの設定を効率的に実装できます。基本的な使い方はClaude Code入門ガイドを参照してください。

Web App Manifest

{
  "name": "My PWA Application",
  "short_name": "MyPWA",
  "description": "高速で信頼性の高いWebアプリケーション",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#3b82f6",
  "orientation": "portrait-primary",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-maskable-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "maskable"
    }
  ]
}

Service Worker の実装

// sw.ts
const CACHE_NAME = "app-cache-v1";
const STATIC_ASSETS = [
  "/",
  "/offline.html",
  "/styles/main.css",
  "/scripts/app.js",
  "/icons/icon-192.png",
];

// Cache static assets on install
self.addEventListener("install", (event: ExtendableEvent) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(STATIC_ASSETS);
    })
  );
  // Activate immediately
  (self as any).skipWaiting();
});

// Delete old caches
self.addEventListener("activate", (event: ExtendableEvent) => {
  event.waitUntil(
    caches.keys().then((keys) => {
      return Promise.all(
        keys
          .filter((key) => key !== CACHE_NAME)
          .map((key) => caches.delete(key))
      );
    })
  );
  (self as any).clients.claim();
});

キャッシュ戦略

// Stale While Revalidate 戦略
self.addEventListener("fetch", (event: FetchEvent) => {
  const { request } = event;
  const url = new URL(request.url);

  // API リクエスト: Network First
  if (url.pathname.startsWith("/api/")) {
    event.respondWith(networkFirst(request));
    return;
  }

  // 静的アセット: Cache First
  if (request.destination === "image" || url.pathname.match(/\.(css|js)$/)) {
    event.respondWith(cacheFirst(request));
    return;
  }

  // HTMLページ: Stale While Revalidate
  event.respondWith(staleWhileRevalidate(request));
});

async function networkFirst(request: Request): Promise<Response> {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch {
    const cached = await caches.match(request);
    return cached || new Response('{"error": "offline"}', {
      status: 503,
      headers: { "Content-Type": "application/json" },
    });
  }
}

async function cacheFirst(request: Request): Promise<Response> {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  const cache = await caches.open(CACHE_NAME);
  cache.put(request, response.clone());
  return response;
}

async function staleWhileRevalidate(
  request: Request
): Promise<Response> {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);

  const fetchPromise = fetch(request).then((response) => {
    cache.put(request, response.clone());
    return response;
  });

  return cached || fetchPromise;
}

オフラインページ

// オフライン検出コンポーネント
import { useState, useEffect } from "react";

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    const handleOnline = () => setIsOnline(true);
    const handleOffline = () => setIsOnline(false);

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return isOnline;
}

function OfflineBanner() {
  const isOnline = useOnlineStatus();

  if (isOnline) return null;

  return (
    <div className="bg-yellow-500 text-white text-center p-2">
      オフラインです。一部の機能が制限されています。
    </div>
  );
}

Service Worker の登録

// Service Worker登録
async function registerServiceWorker() {
  if (!("serviceWorker" in navigator)) {
    console.log("Service Worker not supported");
    return;
  }

  try {
    const registration = await navigator.serviceWorker.register(
      "/sw.js",
      { scope: "/" }
    );

    // 更新チェック
    registration.addEventListener("updatefound", () => {
      const newWorker = registration.installing;
      newWorker?.addEventListener("statechange", () => {
        if (
          newWorker.state === "installed" &&
          navigator.serviceWorker.controller
        ) {
          // 新しいバージョンが利用可能
          showUpdateNotification();
        }
      });
    });

    console.log("Service Worker registered:", registration.scope);
  } catch (error) {
    console.error("Registration failed:", error);
  }
}

Claude Codeでの実装プロンプト

PWA開発をClaude Codeに依頼する例です。パフォーマンスについてはコード分割・遅延読み込みも合わせて参照してください。

既存のReactアプリをPWA化して。
- Web App Manifestの作成
- Service Workerでオフライン対応
- 静的アセットはCache First、APIはNetwork First
- アプリ更新通知の実装
- Lighthouse PWAスコア100点を目指して

PWAの詳細はweb.dev PWAガイドを参照してください。Claude Codeの最新情報は公式ドキュメントで確認できます。

Summary

PWAはWebとネイティブの良いところ取りができる技術です。Claude Codeを使えば、Service Workerのキャッシュ戦略からオフライン対応まで、一貫したPWA実装を効率的に進められます。

#Claude Code #PWA #Service Worker #offline #mobile