Contents
Service Worker とは
Webアプリケーションのバックグラウンドで動作するスクリプト
- ブラウザのメインスレッドとは別に動作
- オフライン動作が可能
- プッシュ通知を受信可能
- バックグラウンドで同期処理が可能
プッシュ通知の流れ
制作手順
Next.jsプロジェクトの作成
PS C:\test> npx create-next-app@latest 1231_push-notification --typescript
Need to install the following packages:
create-next-app@15.1.3
Ok to proceed? (y) y
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like your code inside a `src/` directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to use Turbopack for `next dev`? ... No / Yes
√ Would you like to customize the import alias (`@/*` by default)? ... No / Yes
√ What import alias would you like configured? ... @/*
Creating a new Next.js app in C:\ida_test\1231_push-notification.
web-pushのインストール
# 必要なパッケージのインストール
npm install web-push # プッシュ通知送信用
npm install @types/web-push --save-dev # TypeScript型定義
web-push
は、プッシュ通知を送信するためのNode.jsライブラリです。
- VAPIDキーの生成
- プッシュ通知の認証に必要な鍵ペアを生成
- サーバーとブラウザ間の安全な通信を確保
- 通知の送信
- サーバーからブラウザへプッシュ通知を送信
- 暗号化された通信を管理
VAPID キーの生成
これはプッシュ通知の認証に必要なキーペアです
# web-pushコマンドラインツールをグローバルにインストール
npm install -g web-push
# VAPIDキーを生成
web-push generate-vapid-keys
.env.local(生成されたキーを記載)
# VAPIDキー(web-push generate-vapid-keysで生成したものを設定)
NEXT_PUBLIC_VAPID_PUBLIC_KEY=あなたのパブリックキー
VAPID_PRIVATE_KEY=あなたのプライベートキー
VAPID_SUBJECT=mailto:your-email@example.com # 連絡先メールアドレス
構成ファイル群
プロジェクトのフォルダ構造
push-notification-project/
├── public/
│ ├── service-worker.js # プッシュ通知を受け取るためのService Worker
│ ├── manifest.json # PWAのマニフェストファイル
│ └── icon.png # 通知用のアイコン画像(192x192px推奨)
│
├── src/
│ ├── components/
│ │ └── NotificationTest.tsx # 通知テスト用のコンポーネント
│ │
│ ├── pages/
│ │ ├── index.tsx # メインページ
│ │ ├── api/ # APIルート
│ │ │ ├── subscribe.ts # 購読情報を保存するAPI
│ │ │ └── push.ts # 通知を送信するAPI
│ │ │
│ │ └── _app.tsx # アプリケーションのルート
│ │
│ └── lib/
│ └── pushUtils.ts # 通知関連のユーティリティ関数
│
├── .env.local # 環境変数(VAPIDキーなど)
├── package.json
└── tsconfig.json
package.json
{
...
"scripts": {
"dev": "next dev --experimental-https",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "15.1.3",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"web-push": "^3.6.7"
},
...
}
service-worker.js
// public/service-worker.js
self.addEventListener("install", (event) => {
console.log("Service Worker: インストール完了");
});
self.addEventListener("activate", (event) => {
console.log("Service Worker: アクティブ化完了");
});
self.addEventListener("push", (event) => {
const options = {
body: "プッシュ通知テスト",
icon: "/icon.png",
requireInteraction: true,
renotify: true,
tag: "push-test",
data: {
url: self.location.origin,
},
};
event.waitUntil(self.registration.showNotification("プッシュ通知", options));
});
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow("/"));
});
manifest.json
{
/* アプリケーションの基本情報 */
"name": "プッシュ通知デモ", // アプリの正式名称
"short_name": "通知デモ", // ホーム画面での短い名称
"description": "プッシュ通知のデモアプリケーション",
/* 表示設定 */
"start_url": "/", // 起動時のURL
"display": "standalone", // 表示モード
"background_color": "#ffffff", // 背景色
"theme_color": "#000000", // テーマ色
/* アイコン設定 */
"icons": [
{
"src": "/icon.png",
"sizes": "192x192",
"type": "image/png"
}
],
/* プッシュ通知の許可設定 */
"permissions": [
"notifications"
]
}
src\app\components\ClientWrapper.tsx
'use client';
import dynamic from 'next/dynamic';
const NotificationTest = dynamic(
() => import('@/app/components/NotificationTest'),
{ ssr: false }
);
export default function ClientWrapper() {
return <NotificationTest />;
}
src\components\NotificationTest.tsx
// src/components/NotificationTest.tsx
'use client';
import { useState, useEffect } from 'react';
export default function NotificationTest() {
const [permission, setPermission] = useState<NotificationPermission>('default');
const [registration, setRegistration] = useState<ServiceWorkerRegistration | null>(null);
useEffect(() => {
setPermission(Notification.permission);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(reg => {
console.log('ServiceWorker登録成功:', reg);
setRegistration(reg);
})
.catch(err => console.error('ServiceWorker登録エラー:', err));
}
}, []);
const sendNotification = async () => {
try {
// 通知許可確認
const currentPermission = await Notification.requestPermission();
setPermission(currentPermission);
if (currentPermission !== 'granted') {
alert('通知が許可されていません');
return;
}
console.log('===通知テスト開始===');
// Service Worker通知
if ('serviceWorker' in navigator && registration) {
console.log('SW通知試行');
await registration.showNotification('SW通知', {
body: 'SW経由テスト',
requireInteraction: true,
renotify: true,
tag: 'test-sw'
});
console.log('SW通知完了');
}
// ブラウザ通知
console.log('ブラウザ通知試行');
const notification = new Notification('ブラウザ通知', {
body: 'ブラウザ経由テスト',
requireInteraction: true,
renotify: true,
tag: 'test-browser'
});
notification.onclick = () => {
window.focus();
notification.close();
};
console.log('ブラウザ通知完了');
} catch (error) {
console.error('通知エラー:', error);
}
};
return (
<div className="p-4">
<button
onClick={sendNotification}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
>
テスト通知を送信
</button>
<p className="mt-2">通知許可状態: {permission}</p>
</div>
);
}
src\components\NotificationTest.tsx
// src/components/NotificationTest.tsx
'use client';
import { useState, useEffect } from 'react';
export default function NotificationTest() {
const [permission, setPermission] = useState<NotificationPermission>('default');
const [registration, setRegistration] = useState<ServiceWorkerRegistration | null>(null);
useEffect(() => {
setPermission(Notification.permission);
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(reg => {
console.log('ServiceWorker登録成功:', reg);
setRegistration(reg);
})
.catch(err => console.error('ServiceWorker登録エラー:', err));
}
}, []);
const sendNotification = async () => {
try {
// 通知許可確認
const currentPermission = await Notification.requestPermission();
setPermission(currentPermission);
if (currentPermission !== 'granted') {
alert('通知が許可されていません');
return;
}
console.log('===通知テスト開始===');
// Service Worker通知
if ('serviceWorker' in navigator && registration) {
console.log('SW通知試行');
await registration.showNotification('SW通知', {
body: 'SW経由テスト',
requireInteraction: true,
renotify: true,
tag: 'test-sw'
});
console.log('SW通知完了');
}
// ブラウザ通知
console.log('ブラウザ通知試行');
const notification = new Notification('ブラウザ通知', {
body: 'ブラウザ経由テスト',
requireInteraction: true,
renotify: true,
tag: 'test-browser'
});
notification.onclick = () => {
window.focus();
notification.close();
};
console.log('ブラウザ通知完了');
} catch (error) {
console.error('通知エラー:', error);
}
};
return (
<div className="p-4">
<button
onClick={sendNotification}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
>
テスト通知を送信
</button>
<p className="mt-2">通知許可状態: {permission}</p>
</div>
);
}
プッシュ通知許可
- Windowsシステム通知設定
- Windowsキー + I(設定を開く)
- システム > 通知とアクション
- 「Chrome」を探して許可に設定
- Chromeブラウザ通知
- Chrome右上の⋮ > 設定
- プライバシーとセキュリティ > サイトの設定 > 通知
- または chrome://settings/content/notifications を直接入力
- localhostサイト許可
- localhost:3000にアクセス
- アドレスバー左の🔒をクリック
- 通知設定を「許可」に変更
- または上記Chrome通知設定画面で手動で追加
- 通知送信ボタンを含むコンポーネント
- Service Workerの初期化コードがある場所
pages/api/
またはapp/api/
内の通知関連のAPIハンドラ