pwa について
インストール可能でオフライン動作に対応するプログレッシブ Web アプリの開発者向けリファレンススキルです。マニフェスト、Workbox のサービスワーカー、キャッシュ戦略、インストールプロンプトを解説します。
主な機能
- 01Web App Manifest の設定
- 02Workbox サービスワーカーのパターン
- 03オフラインフォールバックの処理
- 04キャッシュ戦略の選択
- 05インストールプロンプトの処理
- 06本番環境向けチェックリスト
活用シーン
- 01Web アプリへのオフラインアクセス追加
- 02静的アセットと動的データの適切なキャッシュ
- 03アプリのインストールプロンプトの実装
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill pwaインストール後、Claude Code で/pwaを入力して読み込まれたか確認します。
詳細
プログレッシブウェブアプリ
ウェブアプリマニフェスト
{
"name": "My App",
"short_name": "MyApp",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#000000",
"icons": [
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png", "purpose": "any maskable" }
]
}
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="/icons/192.png" />
サービスワーカー(Workbox — 推奨)
// sw.ts (using Workbox)
import { precacheAndRoute } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies';
import { ExpirationPlugin } from 'workbox-expiration';
// Precache app shell
precacheAndRoute(self.__WB_MANIFEST);
// Cache-first for static assets
registerRoute(
({ request }) => request.destination === 'image' || request.destination === 'font',
new CacheFirst({
cacheName: 'static-assets',
plugins: [new ExpirationPlugin({ maxEntries: 100, maxAgeSeconds: 30 * 24 * 60 * 60 })],
})
);
// Network-first for API calls
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new NetworkFirst({
cacheName: 'api-cache',
plugins: [new ExpirationPlugin({ maxEntries: 50, maxAgeSeconds: 5 * 60 })],
})
);
// Stale-while-revalidate for pages
registerRoute(
({ request }) => request.mode === 'navigate',
new StaleWhileRevalidate({ cacheName: 'pages' })
);
登録
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('SW registered:', registration.scope);
});
}
オフライン時のフォールバック
// In service worker
import { setCatchHandler } from 'workbox-routing';
setCatchHandler(async ({ event }) => {
if (event.request.destination === 'document') {
return caches.match('/offline.html');
}
return Response.error();
});
キャッシュ戦略
| 戦略 | 用途 | 鮮度 |
|---|---|---|
| キャッシュ優先 | 静的アセット、フォント、画像 | 古くても可 |
| ネットワーク優先 | API データ、動的ページ | 新しいデータを優先 |
| 古いキャッシュを使用しながら再検証 | 半動的なコンテンツ | 古いデータを使用し、更新 |
| ネットワークのみ | 認証、POST リクエスト | 常に最新 |
| キャッシュのみ | 事前キャッシュされたアプリシェル | 不変 |
インストールプロンプト
let deferredPrompt: BeforeInstallPromptEvent | null = null;
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
showInstallButton();
});
async function installApp() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
console.log(`Install ${outcome}`);
deferredPrompt = null;
}
避けるべきパターン
| 避けるべきパターン | 修正方法 |
|---|---|
| Cache First ですべてをキャッシュする | 動的データには Network First を使用する |
| オフライン時のフォールバックページがない | offline.html を事前キャッシュする |
| キャッシュの有効期限がない | maxEntries/maxAge を指定して ExpirationPlugin を使用する |
| サービスワーカーが認証トークンをキャッシュする | 機密データは決してキャッシュしない |
| サービスワーカーの更新戦略がない | skipWaiting() を使用し、ユーザーに更新を促す |
本番環境向けチェックリスト
- アイコンとテーマを含むウェブアプリマニフェスト
- Workbox の戦略を使用したサービスワーカー
- オフライン時のフォールバックページ
- キャッシュの有効期限ポリシー
- インストールプロンプトの UX
- Lighthouse の PWA 監査に合格
コメント
Markdown 対応