こんにちは!前回のエピソードでは、next-intl
を使って多言語対応を実装し、グローバル市場向けにアプリを拡張しました。今回は、Progressive Web App(PWA)機能を追加して、モバイルでのネイティブアプリのような体験を提供します。next-pwa
を使ってオフラインサポートやプッシュ通知を導入し、ユーザーエンゲージメントを向上させます!
このエピソードのゴール
-
next-pwa
を使ってPWA機能を追加。 - マニフェストファイルとサービスワーカーを設定してオフライン対応を実現。
- プッシュ通知をFirebase Cloud Messaging(FCM)で実装。
- モバイルデバイスでのPWA動作を確認。
必要なもの
- 前回のプロジェクト(
next-ecommerce
)がセットアップ済み。 -
next-pwa
パッケージ。 - Firebaseアカウント(プッシュ通知用)。
- 基本的なTypeScript、React、Next.jsの知識。
ステップ1: next-pwaのセットアップ
next-pwa
は、Next.jsプロジェクトにPWA機能を簡単に追加できるライブラリです。まず、必要なパッケージをインストールします:
npm install next-pwa
next.config.js
を更新してnext-pwa
を統合します:
/** @type {import('next').NextConfig} */
const withNextIntl = require('next-intl/plugin')();
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development', // 開発環境ではPWAを無効化
});
const nextConfig = {
images: {
domains: ['cdn.shopify.com'],
},
};
module.exports = withNextIntl(withPWA(nextConfig));
この設定は:
- PWAのキャッシュファイルを出力先に
public
フォルダを指定。 - 開発環境でのPWA無効化を有効化。
-
next-intl
と共存するように構成。
ステップ2: Webマニフェストの設定
PWAには、アプリのメタデータを定義するmanifest.json
が必要です。public/manifest.json
ファイルを作成:
{
"name": "Next.js eCommerce",
"short_name": "eCommerce",
"description": "A high-performance eCommerce app built with Next.js",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#1d4ed8",
"icons": [
{
"src": "/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
-
start_url
: アプリ起動時のURL。 -
display: standalone
: ネイティブアプリのようなフルスクリーン表示。 -
icons
: ホーム画面に追加されるアイコン。
public
フォルダにicon-192x192.png
とicon-512x512.png
を用意してください(例: ロゴ画像)。デザインツールやオンラインツールで生成可能です。
次に、src/app/[locale]/layout.tsx
にマニフェストをリンク:
import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';
import Navbar from '@/components/Navbar';
import Head from 'next/head';
import '../styles/globals.css';
export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'ja' }];
}
export default async function LocaleLayout({
children,
params: { locale },
}: {
children: React.ReactNode;
params: { locale: string };
}) {
let messages;
try {
messages = (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
const alternateLocales = ['en', 'ja'].filter((l) => l !== locale);
return (
<html lang={locale}>
<Head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#1d4ed8" />
<link rel="alternate" hrefLang={locale} href={`https://yourdomain.com/${locale}`} />
{alternateLocales.map((altLocale) => (
<link
key={altLocale}
rel="alternate"
hrefLang={altLocale}
href={`https://yourdomain.com/${altLocale}`}
/>
))}
<link rel="alternate" hrefLang="x-default" href="https://yourdomain.com/en" />
</Head>
<body>
<NextIntlClientProvider locale={locale} messages={messages}>
<Navbar />
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
ステップ3: サービスワーカーの確認
next-pwa
は、ビルド時にサービスワーカーを自動生成します。public/sw.js
とpublic/workbox-*.js
が生成されることを確認してください。これにより、ページやアセットがキャッシュされ、オフラインでもアプリが動作します。
デフォルトのキャッシュ戦略をカスタマイズしたい場合、next.config.js
に以下を追加:
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development',
runtimeCaching: [
{
urlPattern: /^https?.*/,
handler: 'NetworkFirst',
options: {
cacheName: 'offlineCache',
expiration: {
maxEntries: 200,
},
},
},
],
});
この設定は、ネットワーク優先のキャッシュ戦略を採用し、最大200エントリをキャッシュします。
ステップ4: プッシュ通知の実装
プッシュ通知を追加するために、Firebase Cloud Messaging(FCM)を統合します。
-
Firebaseのセットアップ:
- Firebaseコンソールでプロジェクトを作成。
- Webアプリを追加し、Firebase SDKの設定情報を取得。
-
public/firebase-messaging-sw.js
ファイルを作成し、サービスワーカーでFCMを初期化:
importScripts('https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js');
firebase.initializeApp({
apiKey: 'あなたのAPIキー',
authDomain: 'あなたのドメイン',
projectId: 'あなたのプロジェクトID',
storageBucket: 'あなたのストレージバケット',
messagingSenderId: 'あなたの送信者ID',
appId: 'あなたのアプリID',
measurementId: 'あなたの測定ID',
});
const messaging = firebase.messaging();
-
クライアントサイドの設定:
src/lib/firebase.ts
ファイルを作成:
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};
const app = initializeApp(firebaseConfig);
const messaging = getMessaging(app);
export async function requestNotificationPermission() {
try {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
const token = await getToken(messaging, {
vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY,
});
console.log('FCMトークン:', token);
return token;
}
} catch (error) {
console.error('通知許可エラー:', error);
}
}
export function onMessageListener(callback: (payload: any) => void) {
onMessage(messaging, callback);
}
.env.local
にFirebase設定を追加:
NEXT_PUBLIC_FIREBASE_API_KEY=あなたのAPIキー
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=あなたのドメイン
NEXT_PUBLIC_FIREBASE_PROJECT_ID=あなたのプロジェクトID
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=あなたのストレージバケット
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=あなたの送信者ID
NEXT_PUBLIC_FIREBASE_APP_ID=あなたのアプリID
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=あなたの測定ID
NEXT_PUBLIC_FIREBASE_VAPID_KEY=あなたのVAPIDキー
-
通知のトリガー:
src/components/NotificationButton.tsx
を作成して通知許可をリクエスト:
import { useState } from 'react';
import { requestNotificationPermission, onMessageListener } from '@/lib/firebase';
export default function NotificationButton() {
const [subscribed, setSubscribed] = useState(false);
const handleSubscribe = async () => {
const token = await requestNotificationPermission();
if (token) {
setSubscribed(true);
onMessageListener((payload) => {
alert(`通知: ${payload.notification.title}`);
});
}
};
return (
<button
onClick={handleSubscribe}
disabled={subscribed}
className="bg-primary text-white px-4 py-2 rounded hover:bg-opacity-90 disabled:opacity-50"
>
{subscribed ? '通知を許可済み' : '通知を有効にする'}
</button>
);
}
src/components/Navbar.tsx
にボタンを追加:
import Link from 'next/link';
import { useCartStore } from '@/lib/cartStore';
import NotificationButton from './NotificationButton';
export default function Navbar() {
const items = useCartStore((state) => state.items);
const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);
return (
<nav className="bg-primary text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<Link href="/" className="text-2xl font-bold">
Next.js eCommerce
</Link>
<div className="flex items-center gap-4">
<Link href="/cart" className="relative">
カート
{itemCount > 0 && (
<span className="absolute -top-2 -right-2 bg-red-500 text-white rounded-full px-2 text-sm">
{itemCount}
</span>
)}
</Link>
<NotificationButton />
</div>
</div>
</nav>
);
}
ステップ5: 動作確認
- Firebaseコンソールでプッシュ通知をテスト送信。
- プロジェクトをビルド(
npm run build
)してローカルで実行(npm run start
)。 -
http://localhost:3000
にアクセスし、以下の点を確認:- 「通知を有効にする」ボタンをクリックすると、通知許可ダイアログが表示される。
- アプリをホーム画面に追加できる(Chromeの「インストール」アイコン)。
- オフラインでページにアクセスし、キャッシュされたコンテンツが表示される。
- モバイルデバイスでPWAをインストールし、ネイティブアプリのような動作を確認。
- Firebaseからテスト通知を送信し、受信されることを確認。
エラーがあれば、Firebase設定やサービスワーカーのログを確認してください。
まとめと次のステップ
このエピソードでは、next-pwa
を使ってPWA機能を追加し、オフラインサポートとプッシュ通知を実装しました。これにより、モバイルユーザーにネイティブアプリのような体験を提供できました。
次回のエピソードでは、アプリケーションをVercelにデプロイし、セキュリティ強化と拡張性を確保します。CI/CDの設定やAIを活用した商品レコメンドのアイデアも紹介しますので、引き続きお楽しみに!
この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです!次回のエピソードもお楽しみに!