Use Cases

Como Desenvolver uma PWA (Progressive Web App) com Claude Code

Aprenda a develop a pwa (progressive web app) usando o Claude Code. Inclui exemplos praticos de codigo e orientacao passo a passo.

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