PWA (Progressive Web App)
PWA (Progressive Web App)
Section titled “PWA (Progressive Web App)”PWA(Progressive Web App)は、Webアプリケーションにネイティブアプリのような体験を提供する技術です。Next.jsでは、next-pwaを使用してPWAを実装できます。
なぜPWAが必要なのか
Section titled “なぜPWAが必要なのか”従来のWebアプリケーションの課題
Section titled “従来のWebアプリケーションの課題”問題のあるWebアプリケーション:
// オフライン時に動作しないexport default function App() { const [data, setData] = useState(null);
useEffect(() => { fetch('/api/data') .then(response => response.json()) .then(data => setData(data)); }, []);
// 問題点: // - オフライン時に動作しない // - インストールできない // - プッシュ通知ができない // - 起動が遅い}PWAの解決:
// Service Workerによるオフライン対応// インストール可能// プッシュ通知対応// 高速な起動
// メリット:// - オフラインでも動作// - ホーム画面に追加可能// - プッシュ通知が可能// - ネイティブアプリのような体験メリット:
- オフライン対応: インターネット接続がなくても動作
- インストール可能: ホーム画面に追加可能
- プッシュ通知: ユーザーに通知を送信
- 高速な起動: キャッシュによる高速な読み込み
next-pwaの設定
Section titled “next-pwaの設定”依存関係の追加
Section titled “依存関係の追加”npm install next-pwaNext.js設定の更新
Section titled “Next.js設定の更新”const withPWA = require('next-pwa')({ dest: 'public', register: true, skipWaiting: true, disable: process.env.NODE_ENV === 'development',});
module.exports = withPWA({ // 他の設定});マニフェストファイル
Section titled “マニフェストファイル”public/manifest.json:
{ "name": "My PWA App", "short_name": "MyApp", "description": "My Progressive Web App", "start_url": "/", "display": "standalone", "background_color": "#ffffff", "theme_color": "#0070f3", "icons": [ { "src": "/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ]}マニフェストの読み込み
Section titled “マニフェストの読み込み”export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="ja"> <head> <link rel="manifest" href="/manifest.json" /> <meta name="theme-color" content="#0070f3" /> </head> <body>{children}</body> </html> );}Service Worker
Section titled “Service Worker”カスタムService Worker
Section titled “カスタムService Worker”public/sw.js:
// Service Workerのカスタムロジックself.addEventListener('install', (event) => { event.waitUntil( caches.open('my-cache').then((cache) => { return cache.addAll([ '/', '/styles.css', '/script.js', ]); }) );});
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { return response || fetch(event.request); }) );});オフライン対応
Section titled “オフライン対応”'use client';
import { useEffect, useState } from 'react';
export function useOnlineStatus() { const [isOnline, setIsOnline] = useState(true);
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;}'use client';
import { useOnlineStatus } from '@/hooks/useOnlineStatus';
export default function OfflineIndicator() { const isOnline = useOnlineStatus();
if (isOnline) return null;
return ( <div role="alert" aria-live="polite"> <p>オフラインです。一部の機能が制限される場合があります。</p> </div> );}インストールプロンプト
Section titled “インストールプロンプト”'use client';
import { useEffect, useState } from 'react';
export default function InstallPrompt() { const [deferredPrompt, setDeferredPrompt] = useState<any>(null); const [showPrompt, setShowPrompt] = useState(false);
useEffect(() => { const handler = (e: Event) => { e.preventDefault(); setDeferredPrompt(e); setShowPrompt(true); };
window.addEventListener('beforeinstallprompt', handler);
return () => { window.removeEventListener('beforeinstallprompt', handler); }; }, []);
const handleInstall = async () => { if (!deferredPrompt) return;
deferredPrompt.prompt(); const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') { console.log('User accepted the install prompt'); }
setDeferredPrompt(null); setShowPrompt(false); };
if (!showPrompt) return null;
return ( <div> <p>このアプリをインストールしますか?</p> <button onClick={handleInstall}>インストール</button> <button onClick={() => setShowPrompt(false)}>キャンセル</button> </div> );}プッシュ通知
Section titled “プッシュ通知”'use client';
import { useEffect, useState } from 'react';
export function usePushNotification() { const [permission, setPermission] = useState<NotificationPermission>('default');
useEffect(() => { if ('Notification' in window) { setPermission(Notification.permission); } }, []);
const requestPermission = async () => { if ('Notification' in window) { const permission = await Notification.requestPermission(); setPermission(permission); return permission; } return 'denied'; };
const showNotification = (title: string, options?: NotificationOptions) => { if (permission === 'granted') { new Notification(title, options); } };
return { permission, requestPermission, showNotification, };}実践的な例: PWAアプリ
Section titled “実践的な例: PWAアプリ”import { Metadata } from 'next';
export const metadata: Metadata = { manifest: '/manifest.json', themeColor: '#0070f3', appleWebApp: { capable: true, statusBarStyle: 'default', title: 'My PWA App', }, viewport: { width: 'device-width', initialScale: 1, maximumScale: 1, },};
export default function RootLayout({ children,}: { children: React.ReactNode;}) { return ( <html lang="ja"> <head> <link rel="apple-touch-icon" href="/icon-192x192.png" /> </head> <body>{children}</body> </html> );}Next.jsでPWAを実装するポイント:
- next-pwa: PWA実装のためのライブラリ
- マニフェスト: アプリのメタデータとアイコン
- Service Worker: オフライン対応とキャッシング
- インストールプロンプト: ユーザーにインストールを促す
- プッシュ通知: ユーザーに通知を送信
PWAは、Webアプリケーションにネイティブアプリのような体験を提供する強力な技術です。適切に実装することで、ユーザー体験を大幅に向上させることができます。