こんにちは!前回のエピソードでは、LMSのパフォーマンスを最適化し、Core Web Vitalsのスコアを向上させました。今回は、LMSをProgressive Web App(PWA)に変換して、モバイルでのネイティブアプリのような体験を提供します。next-pwa
を使ってオフライン機能を追加し、manifest.json
とサービスワーカーを設定、プッシュ通知をOneSignalで統合します。これで、ユーザーはアプリをホーム画面に追加し、オフラインでも利用できるようになります!
このエピソードのゴール
-
next-pwa
を使ってPWA機能を追加。 -
manifest.json
とサービスワーカーを設定し、オフライン対応を実現。 - OneSignalでプッシュ通知を統合。
- モバイルデバイスでPWAの動作を確認。
必要なもの
- 前回のプロジェクト(
next-lms
)がセットアップ済み。 - Supabaseプロジェクト(既存のテーブル設定済み)。
-
next-pwa
と@onesignal/onesignal-react
パッケージ。 - OneSignalアカウント(無料プランで十分)。
- 基本的なTypeScript、React、Next.jsの知識。
ステップ1: next-pwa
のセットアップ
next-pwa
をインストールし、PWA機能を有効化します。
-
パッケージのインストール:
以下のコマンドでnext-pwa
をインストール:
npm install next-pwa
-
Next.js設定の更新:
next.config.js
を更新してPWAを有効化:
const withPWA = require('next-pwa')({
dest: 'public',
disable: process.env.NODE_ENV === 'development', // 開発環境では無効
register: true,
skipWaiting: true,
});
module.exports = withPWA({
reactStrictMode: true,
images: {
domains: ['via.placeholder.com'], // 必要に応じてSupabase Storageのドメインを追加
},
});
このコードは:
- PWAのキャッシュ先を
public
フォルダに設定。 - 開発環境ではPWAを無効化。
- サービスワーカーを自動登録。
-
サービスワーカーのカスタマイズ:
public/workbox-*.js
が自動生成されるが、カスタムキャッシュ戦略が必要な場合、public/sw.js
を作成(今回はデフォルトを使用)。
ステップ2: manifest.json
の設定
PWAのメタデータを定義するmanifest.json
を追加します。
-
manifest.json
の作成:
public/manifest.json
を作成:
{
"name": "Next.js LMS",
"short_name": "LMS",
"description": "Next.jsとSupabaseで構築されたオンライ学習プラットフォーム",
"start_url": "/",
"display": "standalone",
"background_color": "#f3f4f6",
"theme_color": "#2563eb",
"icons": [
{
"src": "/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
-
アイコンの準備:
- 192x192と512x512のPNGアイコン(例:
icon-192x192.png
,icon-512x512.png
)をpublic
フォルダに追加。 - アイコンはブランドロゴやアプリのテーマに合わせたデザインを使用(例: LMSのロゴ)。
- 192x192と512x512のPNGアイコン(例:
-
マニフェストのリンク:
src/app/layout.tsx
を更新してmanifest.json
をリンク:
import '../styles/globals.css';
import Link from 'next/link';
import { supabaseServer } from '@/lib/supabase';
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = supabaseServer();
const { data: { user } } = await supabase.auth.getUser();
return (
<html lang="ja" className={inter.className}>
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#2563eb" />
<link rel="apple-touch-icon" href="/icon-192x192.png" />
</head>
<body className="bg-gray-100">
<header className="bg-primary text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<h1 className="text-2xl font-bold">
<Link href="/">Next.js LMS</Link>
</h1>
<nav className="flex gap-4">
<Link href="/courses" className="hover:underline">コース</Link>
{user ? (
<>
<Link href="/dashboard" className="hover:underline">ダッシュボード</Link>
<form
action={async () => {
'use server';
await supabase.auth.signOut();
}}
className="inline"
>
<button className="hover:underline">ログアウト</button>
</form>
</>
) : (
<>
<Link href="/login" className="hover:underline">ログイン</Link>
<Link href="/register" className="hover:underline">登録</Link>
</>
)}
</nav>
</div>
</header>
{children}
</body>
</html>
);
}
このコードは:
-
manifest.json
をリンク。 - テーマカラーとApple Touch Iconを指定。
ステップ3: オフライン対応の確認
サービスワーカーがオフラインキャッシュを提供します。
-
キャッシュ戦略の確認:
-
next-pwa
はデフォルトで静的アセット(HTML、CSS、JS、画像)をキャッシュ。 -
public/sw.js
でカスタムキャッシュが必要な場合、以下を参考に追加:
-
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
-
オフライン動作のテスト:
- 開発サーバーを停止し、ブラウザのキャッシュをクリア。
- Chrome DevToolsの「Application」→「Service Workers」でキャッシュを確認。
- オフラインモード(DevToolsの「Network」→「Offline」)でページをリロードし、コース一覧やホームが表示されることを確認。
ステップ4: OneSignalでプッシュ通知の統合
OneSignalを使ってプッシュ通知を追加します。
-
OneSignalアカウントの設定:
- OneSignalでアカウントを作成。
- 新しいアプリを作成し、Web Pushを設定。
- アプリIDを取得。
-
パッケージのインストール:
OneSignalのReact SDKをインストール:
npm install @onesignal/onesignal-react
-
OneSignalの初期化:
src/app/_app.tsx
(またはグローバルコンポーネント)を作成し、OneSignalを初期化:
'use client';
import { useEffect } from 'react';
import OneSignal from '@onesignal/onesignal-react';
export default function OneSignalInit() {
useEffect(() => {
OneSignal.init({
appId: 'あなたのOneSignalアプリID',
allowLocalhostAsSecureOrigin: true, // 開発環境用
}).then(() => {
OneSignal.showSlidedownPrompt();
});
return () => {
OneSignal.removeAllListeners();
};
}, []);
return null;
}
-
初期化コンポーネントの統合:
src/app/layout.tsx
にOneSignalInit
を追加:
import '../styles/globals.css';
import Link from 'next/link';
import { supabaseServer } from '@/lib/supabase';
import { Inter } from 'next/font/google';
import OneSignalInit from '@/components/OneSignalInit';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const supabase = supabaseServer();
const { data: { user } } = await supabase.auth.getUser();
return (
<html lang="ja" className={inter.className}>
<head>
<link rel="manifest" href="/manifest.json" />
<meta name="theme-color" content="#2563eb" />
<link rel="apple-touch-icon" href="/icon-192x192.png" />
</head>
<body className="bg-gray-100">
<OneSignalInit />
<header className="bg-primary text-white p-4">
<div className="container mx-auto flex justify-between items-center">
<h1 className="text-2xl font-bold">
<Link href="/">Next.js LMS</Link>
</h1>
<nav className="flex gap-4">
<Link href="/courses" className="hover:underline">コース</Link>
{user ? (
<>
<Link href="/dashboard" className="hover:underline">ダッシュボード</Link>
<form
action={async () => {
'use server';
await supabase.auth.signOut();
}}
className="inline"
>
<button className="hover:underline">ログアウト</button>
</form>
</>
) : (
<>
<Link href="/login" className="hover:underline">ログイン</Link>
<Link href="/register" className="hover:underline">登録</Link>
</>
)}
</nav>
</div>
</header>
{children}
</body>
</html>
);
}
-
プッシュ通知のテスト:
- OneSignalダッシュボードでテスト通知を作成(例: 「新しいコースが追加されました!」)。
- ブラウザで通知許可プロンプトが表示され、許可後に通知を受信。
- モバイルデバイスで通知が正しく表示されることを確認。
ステップ5: モバイルでのPWA動作確認
PWAがモバイルでネイティブアプリのように動作することを確認します。
-
ビルドとサーバー起動:
- プロジェクトをビルドし、プロダクションサーバーを起動:
npm run build
npm run start
-
モバイルデバイスでのテスト:
- スマートフォンで
http://localhost:3000
にアクセス(ローカルネットワーク経由)。 - ブラウザの「ホーム画面に追加」を選択し、アプリをインストール。
- アプリを起動し、以下の点を確認:
- フルスクリーン表示(
display: standalone
)。 - オフラインモードでコース一覧が表示。
- プッシュ通知が受信可能。
- フルスクリーン表示(
- アイコンとテーマカラーが正しく反映。
- スマートフォンで
-
LighthouseのPWA監査:
- Lighthouseの「Progressive Web App」カテゴリで監査を実行。
- 「Installable」「Service Worker」「Manifest」などの項目が緑(合格)であることを確認。
ステップ6: 動作確認
- 開発サーバーを起動(またはビルド後):
npm run dev
- ブラウザで
http://localhost:3000
にアクセス:- PWAインストールプロンプトが表示(Chrome/Edge)。
-
manifest.json
が正しく読み込まれ、アイコンとテーマカラーが反映。 - オフラインモードでページが表示。
- OneSignalの通知許可プロンプトが表示され、通知を受信。
- モバイルデバイスで:
- ホーム画面に追加後、アプリがネイティブのように動作。
- 通知が正しく表示。
- LighthouseでPWAスコアを確認(90以上を目指す)。
エラーがあれば、manifest.json
の設定、OneSignalのアプリID、またはサービスワーカーのキャッシュを確認してください。
まとめと次のステップ
このエピソードでは、LMSをProgressive Web App(PWA)に変換しました。next-pwa
でオフライン機能を追加し、manifest.json
とサービスワーカーを設定、OneSignalでプッシュ通知を統合しました。これで、ユーザーはモバイルでネイティブアプリのような体験を得られます!
次回の最終エピソードでは、LMSをVercelにデプロイし、セキュリティを強化、CI/CDを設定します。さらに、AIによるコース推薦や学習分析などの拡張アイデアも提案しますので、引き続きお楽しみに!
この記事が役に立ったと思ったら、ぜひ「いいね」を押して、ストックしていただければ嬉しいです!次回のエピソードもお楽しみに!