0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

firebaseからNext.jsのに向けてWebPushを送ってみる

Posted at

はじめに

こんにちは、エンジニアのkeitaMaxです。

firebaseからNext.jsのプロジェクトに向けてPushを送ってみようと思います。

基本的にこの記事を元に行っていこうと思います

firebaseの設定

まうfirebaseに新しいプロジェクトを作成してAPIKeyなどを取得するようにします。

プロジェクトの作成

スクリーンショット 2025-06-14 10.11.04.png

アプリの登録

スクリーンショット 2025-06-14 10.11.56.png

スクリーンショット 2025-06-14 10.12.47.png

この画面でapiKeyなどが出てくるのでメモっておきましょう

Keyの作成

プロジェクトの設定>Cloud Messaging>Generate key pair

スクリーンショット 2025-06-14 10.17.02.png

スクリーンショット 2025-06-14 10.18.14.png

インストール

以下でfirebaseのライブラリをインストールします。

npm install firebase

コード

基本的には参考元と同様にしますが、同じだとESLintでエラーがでていたので、少し修正しています。

src/firebase/config.ts
"use client"

import { initializeApp } from 'firebase/app'
import { getMessaging } 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,
}

const app = initializeApp(firebaseConfig)

let messaging: ReturnType<typeof getMessaging> | null = null

if (typeof window !== 'undefined') {
  try {
    messaging = getMessaging(app)
  } catch (error) {
    console.error('Firebase Messaging初期化エラー:', error)
  }
}

export { messaging }
src/firebase/useFCM.ts
"use client"

import { useEffect, useState } from "react"
import { MessagePayload, onMessage } from "firebase/messaging"
import { messaging } from "~/firebase/config"
import useFCMToken from "./useFCMToken"

export const useFCM = () => {
  const fcmToken = useFCMToken()
  const [messages, setMessages] = useState<MessagePayload[]>([])

  useEffect(() => {
    if (typeof window === 'undefined') return
    if (!("serviceWorker" in navigator)) return
    if (!messaging) return

    try {
      const unsubscribe = onMessage(messaging, (payload) => {
        setMessages((messages) => [...messages, payload])
      })

      return () => unsubscribe()
    } catch (error) {
      console.error('FCM初期化エラー:', error)
    }
  }, [fcmToken])

  return { fcmToken, messages }
}

src/firebase/useFCMToken.ts
"use client"

import { useEffect, useState } from "react"
import { getToken, isSupported } from "firebase/messaging"
import { messaging } from "~/firebase/config"
import useNotificationPermissionStatus from "./useNotificationPermissionStatus"

const useFCMToken = () => {
  const permission = useNotificationPermissionStatus()
  const [fcmToken, setFcmToken] = useState<string | null>(null)

  useEffect(() => {
    const retrieveToken = async () => {
      if (typeof window === "undefined") return
      if (!("serviceWorker" in navigator)) return
      if (permission !== "granted") return
      if (!messaging) return

      try {
        const isFCMSupported = await isSupported()
        if (!isFCMSupported) return

        // Service Workerの登録
        await navigator.serviceWorker.register('/web-push/firebase-messaging-sw.js', {
          scope: '/web-push/'
        })

        const token = await getToken(messaging, {
          vapidKey: process.env.NEXT_PUBLIC_FIREBASE_VAPID_KEY,
          serviceWorkerRegistration: await navigator.serviceWorker.getRegistration()
        })
        setFcmToken(token)
      } catch (error) {
        console.error('FCMトークン取得エラー:', error)
      }
    }

    retrieveToken()
  }, [permission])

  return fcmToken
}

export default useFCMToken

src/firebase/useNotificationPermissionStatus.ts
"use client"

import { useEffect, useState } from "react"

type NotificationPermission = "default" | "granted" | "denied"
// type PermissionName = "notifications"

const useNotificationPermissionStatus = () => {
  const [permission, setPermission] = useState<NotificationPermission>("default")

  useEffect(() => {
    if (typeof window === "undefined") return
    if (!("Notification" in window)) return

    const handler = () => setPermission(Notification.permission)
    handler()

    try {
      Notification.requestPermission().then(handler)
      navigator.permissions
        .query({ name: "notifications" })
        .then((notificationPerm) => {
          notificationPerm.onchange = handler
        })
        .catch((error) => {
          console.error('通知許可状態の取得エラー:', error)
        })
    } catch (error) {
      console.error('通知許可のリクエストエラー:', error)
    }
  }, [])

  return permission
}

export default useNotificationPermissionStatus

.env
NEXT_PUBLIC_FIREBASE_APP_ID=
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=
NEXT_PUBLIC_FIREBASE_VAPID_KEY=

firebase-messaging-sw.js
// eslint-disable-next-line no-undef
importScripts("https://www.gstatic.com/firebasejs/8.8.0/firebase-app.js")
// eslint-disable-next-line no-undef
importScripts("https://www.gstatic.com/firebasejs/8.8.0/firebase-messaging.js")

// eslint-disable-next-line no-undef
const firebaseConfig = {
  apiKey: "AIzaSyBblSP-",
  authDomain: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
}

// eslint-disable-next-line no-undef
firebase.initializeApp(firebaseConfig)
// eslint-disable-next-line no-undef
const messaging = firebase.messaging()

messaging.onBackgroundMessage((payload) => {
  console.log(
    "[firebase-messaging-sw.js] Received background message ",
    payload
  )
  const notificationTitle = payload.notification.title
  const notificationOptions = {
    body: payload.notification.body,
    icon: "/web-push/logo.png",
  }
  self.registration.showNotification(notificationTitle, notificationOptions)
})

firebase-messaging-sw.jsはGitには上げていません。

以下のようにGitHubActionsでbuildする時に作成するようにしました。

deploy.yml
name: Deploy to GitHub Pages

# on: [pull_request]
on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: 'npm'
      - name: Copy .env.example
        run: cp .env.example .env
      - name: Inject Firebase secrets into .env
        run: |
          echo "NEXT_PUBLIC_FIREBASE_API_KEY=${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_PROJECT_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_APP_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }}" >> .env
          echo "NEXT_PUBLIC_FIREBASE_VAPID_KEY=${{ secrets.NEXT_PUBLIC_FIREBASE_VAPID_KEY }}" >> .env
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Setup Pages
        uses: actions/configure-pages@v4
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./out

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

テスト

実際にテストでPushを送ってみましょう。

npm run devをしてNext.jsのアプリケーションを起動させておきます

firebase上でPushを作成します。

スクリーンショット 2025-06-14 10.24.31.png

スクリーンショット 2025-06-14 10.26.02.png

これでメッセージの準備はできました。

次にNextjs側のトークンを取得します。
npm run dev すると出てくるトークンをコピーして入力します。

スクリーンショット 2025-06-14 10.26.33.png

これをfirebaseの方に入力します。

スクリーンショット 2025-06-14 10.26.58.png

この状態でテストとすると、Push通知が送られてきて

スクリーンショット 2025-06-14 10.27.28.png

このような感じでメッセージが表示されます

おわりに

この記事での質問や、間違っている、もっといい方法があるといったご意見などありましたらご指摘していただけると幸いです。

最後まで読んでいただきありがとうございました!

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?