Implementing Firebase Push Notifications in Next.js: A Step-by-Step Guide

Muhaymin Bin Mehmood

Muhaymin Bin Mehmood

· 5 min read
Implementing Firebase Push Notifications in Next.js 13: A Step-by-Step Guide banner image
Implementing Firebase Push Notifications in Next.js 13: A Step-by-Step Guide banner image

Push notifications are a powerful way to engage users with your Next.js application even when they're not actively browsing. By leveraging Firebase Cloud Messaging (FCM), you can send targeted messages, alerts, and updates directly to user devices. This guide will walk you through the integration process, providing clear explanations and code examples along the way.

Prerequisites

  • A Next.js 13 project
  • A Firebase project with Cloud Messaging enabled

1. Setting Up Firebase

  • If you don't have a Firebase project yet, head to https://console.firebase.google.com/ and create one.
  • Within your Firebase project, navigate to the Cloud Messaging section and enable it. Note down your Server Key and VAPID Key (Web Push Certificate).

2. Install Firebase Dependencies

Open your terminal or command prompt and navigate to your Next.js project's root directory. Install the necessary Firebase packages using npm or yarn:

npm install firebase

3. Create firebase.ts (or Equivalent)

In your project's root directory (outside of any components), create a file named firebase.ts (or adjust the filename if using a different approach). This file will house your Firebase configuration:

import { initializeApp } from 'firebase/app';

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID",
};

// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);

export default firebaseApp;

Explanation:

  • This file imports the initializeApp function from the firebase/app module and initializes Firebase using your project's configuration details.
  • Replace the placeholder values with your actual Firebase project credentials.

4. Create firebase-messaging-sw.js

In your Next.js project's public directory, create a file named firebase-messaging-sw.js. This service worker will handle push notification behavior:

importScripts('https://www.gstatic.com/firebasejs/10.5.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.5.0/firebase-messaging-compat.js');

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_AUTH_DOMAIN",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_STORAGE_BUCKET",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID",
  measurementId: "YOUR_MEASUREMENT_ID",
};

firebase.initializeApp(firebaseConfig);

class CustomPushEvent extends Event {
    constructor(data) {
        super('push');

        Object.assign(this, data);
        this.custom = true;
    }
}

/*
 * Overrides push notification data, to avoid having 'notification' key and firebase blocking
 * the message handler from being called
 */
self.addEventListener('push', (e) => {
    // Skip if event is our own custom event
    if (e.custom) return;

    // Kep old event data to override
    const oldData = e.data;

    // Create a new event to dispatch, pull values from notification key and put it in data key,
    // and then remove notification key
    const newEvent = new CustomPushEvent({
        data: {
            ehheh: oldData.json(),
            json() {
                const newData = oldData.json();
                newData.data = {
                    ...newData.data,
                    ...newData.notification,
                };
                delete newData.notification;
                return newData;
            },
        },
        waitUntil: e.waitUntil.bind(e),
    });

    // Stop event propagation
    e.stopImmediatePropagation();

    // Dispatch the new wrapped event
    dispatchEvent(newEvent);
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
    // console.log('[firebase-messaging-sw.js] Received background message ', payload);

    const { title, body, image, icon, ...restPayload } = payload.data;
    const notificationOptions = {
        body,
        icon: image || '/icons/firebase-logo.png', // path to your "fallback" firebase notification logo
        data: restPayload,
    };
    return self.registration.showNotification(title, notificationOptions);
});

self.addEventListener('notificationclick', (event) => {
    if (event?.notification?.data && event?.notification?.data?.link) {
        self.clients.openWindow(event.notification.data.link);
    }

    // close notification after click
    event.notification.close();
});

Explanation:

  • Imports Firebase JavaScript SDK libraries.
  • Imports the initialized Firebase app instance.
  • Initializes Firebase within the service worker for correct access.
  • Defines a CustomPushEvent class to handle structured notification data.
  • Listens for the push event in the service worker.
  • Extracts notification data from the payload.
  • Creates a new CustomPushEvent with structured data for consistency.
  • Stops event propagation and dispatches the new event.
  • Accesses the messaging instance and listens for background messages on onBackgroundMessage.
  • Displays a notification using self.registration.showNotification.
  • Listens for the notificationclick

5. Create useFCMToken.ts (Custom Hook):

'use client'
import { useEffect, useState } from 'react';
import { getMessaging, getToken } from 'firebase/messaging';
import firebaseApp from '../firebase/firebase';

const useFcmToken = () => {
  const [token, setToken] = useState('');
  const [notificationPermissionStatus, setNotificationPermissionStatus] = useState('');

  useEffect(() => {
    const retrieveToken = async () => {
      try {
        if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
          const messaging = getMessaging(firebaseApp);

          // Request notification permission
          const permission = await Notification.requestPermission();
          setNotificationPermissionStatus(permission);

          if (permission === 'granted') {
            const currentToken = await getToken(messaging, {
              vapidKey: 'YOUR_VAPID_KEY', // Replace with your Firebase project's VAPID key
            });
            if (currentToken) {
              setToken(currentToken);
            } else {
              console.log('No registration token available. Request permission to generate one.');
            }
          }
        }
      } catch (error) {
        console.log('Error retrieving token:', error);
      }
    };

    retrieveToken();
  }, []);

  return { fcmToken, notificationPermissionStatus };
};

export default useFcmToken;

Explanation:

  • This custom hook handles retrieving the FCM token and notification permission status.
  • It uses the useState hook to manage the state of the token and permission status.
  • The useEffect hook is used to fetch the token and permission on client-side rendering.
  • It checks for browser compatibility and service worker support.
  • It requests notification permission and updates the state accordingly.
  • If permission is granted, it retrieves the FCM token using getToken.
  • The retrieved token or an error message is stored in the state.

6. Create firebaseForeground.js (Handling Foreground Notifications):

'use client'
import useFcmToken from "@/utils/hooks/useFCMToken";
import { getMessaging, onMessage } from 'firebase/messaging';
import firebaseApp from '../firebase/firebase';
import { useEffect } from 'react';

export default function FcmTokenComp() {
  const { fcmToken, notificationPermissionStatus } = useFcmToken();

  useEffect(() => {
    if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
      if (notificationPermissionStatus === 'granted') {
        const messaging = getMessaging(firebaseApp);
        const unsubscribe = onMessage(messaging, (payload) => console.log('Foreground push notification received:', payload));
        return () => {
          unsubscribe(); // Unsubscribe from the onMessage event on cleanup
        };
      }
    }
  }, [notificationPermissionStatus]);

  return null; // This component is primarily for handling foreground notifications
}

Explanation:

  • This component handles foreground push notifications received while the user is actively browsing the application.
  • It imports the useFcmToken hook to access the token and permission status.
  • It utilizes the useEffect hook to listen for foreground messages using onMessage when permission is granted.
  • The unsubscribe function cleans up the event listener when the component unmounts.

7. Integrate Components and Handle Notifications:

- Render FcmTokenComp in a client-side rendered component:

import FcmTokenComp from '../utils/hooks/firebaseForeground';

function MyApp({ Component, pageProps }) {
  return (
    <div>
      <FcmTokenComp /> {/* Render for foreground notification handling */}
      <Component {...pageProps} />
    </div>
  );
}

Conclusion

Firebase Cloud Messaging (FCM) provides a powerful and convenient way to engage users with your Next.js 13 application even when they're not actively browsing. By following the steps outlined in this guide, you can seamlessly integrate FCM to send targeted notifications, alerts, and updates directly to user devices.

This guide provided a detailed explanation of the setup process, including code examples and explanations. We explored handling both foreground and background notifications, leveraging custom hooks for efficient token management, and integrating with your client-side components.

Remember to consider additional aspects like advanced notification customization, security best practices, error handling, and testing strategies for a robust implementation. By effectively utilizing FCM, you can significantly enhance user engagement and keep your audience informed with timely and relevant messages.

This integration opens doors for various use cases, from real-time updates and abandoned cart reminders to personalized promotions and targeted marketing campaigns. With a little planning and effort, FCM can become a valuable tool in your Next.js development arsenal, fostering a more interactive and engaging user experience.

Muhaymin Bin Mehmood

About Muhaymin Bin Mehmood

Front-end Developer skilled in the MERN stack, experienced in web and mobile development. Proficient in React.js, Node.js, and Express.js, with a focus on client interactions, sales support, and high-performance applications.

Copyright © 2024 Mbloging. All rights reserved.