How To Add Firebase Push Notifications In Next JS (React)
We will setup push notifications on NextJS by using Firebase Cloud Messaging which will receive notifications even in the background or when the app is closed.
Setting up FCM
First you have to create a project in firebase (https://console.firebase.google.com) to integrate FCM. From the project settings on console, you can add Firebase App to your web application.
You will find the following config in Firebase console > Project Settings > General > Firebase SDK snippet > config
var config = {
apiKey: ***************,
authDomain: ************,
databaseURL: ***********,
projectId: **********,
storageBucket: **********,
messagingSenderId: **********
};
From the above code snippet, you just need the messagingSenderId. So you can remove the rest.
Setting up the Firebase SDK
Now that you have firebaseConfig all set up, it’s time to use that in your application. Install firebase plugin in your NextJS app using npm or yarn.
yarn add firebase
# or
npm install firebase
Connecting with Firebase Cloud Messaging
Create a webPush.js file inside your project directory where you will initialize and get the FCM token. For storing FCM token we are using localforage.
yarn add localforage
# or
npm install localforage
localForage improves the offline experience of your web app by using asynchronous storage (IndexedDB or WebSQL) with a simple, localStorage
-like API.
Add the below lines of code in webPush.js
import ‘firebase/messaging’;
import firebase from ‘firebase/app’;
import localforage from ‘localforage’;const firebaseCloudMessaging = {//checking whether token is available in indexed DBtokenInlocalforage: async () => {
return localforage.getItem(‘fcm_token’);
},//initializing firebase appinit: async function () {if (!firebase.apps.length) {firebase.initializeApp({
messagingSenderId: **********
});try {const messaging = firebase.messaging();
const tokenInLocalForage = await this.tokenInlocalforage();//if FCM token is already there just return the tokenif (tokenInLocalForage !== null) {
return tokenInLocalForage;
}//requesting notification permission from browserconst status = await Notification.requestPermission();if (status && status === ‘granted’) {//getting token from FCMconst fcm_token = await messaging.getToken();if (fcm_token) {//setting FCM token in indexed db using localforagelocalforage.setItem(‘fcm_token’, token);console.log(‘fcm token’, token);//return the FCM token after saving itreturn token;
}
}
} catch (error) {
console.error(error);
return null;
}
}
},
};export { firebaseCloudMessaging };
Adding Service Workers
Service workers acts like a middleware between your browser and external content (like images, fonts, APIs, etc.) by intercepting HTTP requests made by your browser. This can lead to a number of different strategies for returning assets to users such as initially showing low-res images before swapping in the correct version or implementing a stale-while-revalidate pattern for regularly updated content.
In case of push notifications, Push messages are delivered to a Service Worker that runs in the origin of the web application, which can use the information in the message to update local state or display a notification to the user.
Scope of a Service Worker
Service Worker is scoped to ./
from the path on which it is loaded, by default. For example, in our Next JS application a service worker loaded from app.com/.next/service-worker.js
would be scoped to the /.next
path.
This is ok for a lot of use cases. However, the service worker to be registered would be loaded from the root scope (https://my.app.com/.next/service-worker.js
) which, in Next JS apps, requires a custom server implementation.
Creating a Custom Service Worker
The purpose of creating this service worker is to cache the FCM initialization.
Create the following code in the filepublic/firebase-messaging-sw.js (or) static/firebase-messaging-sw.js
importScripts(‘https://www.gstatic.com/firebasejs/7.9.1/firebase-app.js');importScripts(‘https://www.gstatic.com/firebasejs/7.9.1/firebase-messaging.js');if (!firebase.apps.length) {firebase.initializeApp({
messagingSenderId: **********
});firebase.messaging();//background notifications will be received herefirebase.messaging().setBackgroundMessageHandler((payload) => console.log(‘payload’, payload));
}
importScripts() tells your service worker script to pause its current execution, download additional code from a given URL, and run it to completion in the current global scope. Once that’s done, the main service worker script resumes execution.
Setting Up a Custom Server
Babel takes care of the Automation of server restart, Code Transpiling, Hot Reloading etc.. But unfortunately the default setup doesn’t allow us an easy way to scope the service worker to our root path so a custom server is needed.
The following server.js
is enough to make things work as expected:
const express = require(‘express’);
const next = require(‘next’);
const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== ‘production’;
const app = next({ dev });const handle = app.getRequestHandler();app.prepare().then(() => {
const server = express();
server.get(‘/service-worker.js’, (req, res) => {
app.serveStatic(req, res, ‘./.next/service-worker.js’);
});//scoping the service workersconst serviceWorkers = [
{
filename: ‘service-worker.js’,
path: ‘./.next/service-worker.js’,
},{
filename: ‘firebase-messaging-sw.js’,
path: ‘./public/firebase-messaging-sw.js’,
},];serviceWorkers.forEach(({ filename, path }) => {
server.get(`/${filename}`, (req, res) => {
app.serveStatic(req, res, path);
});
});server.get(‘*’, (req, res) => {
return handle(req, res);
});server.listen(port, (err) => {
if (err) throw err;
});});
Initializing And Receiving Foreground Push Messages
After creating and setting up the service worker we need to initialize firebase config to receive foreground messages which we have already created in webPush.js.
import React, { useEffect } from ‘react’;import { firebaseCloudMessaging } from ‘./webPush’;function App() {useEffect(() => {
setToken();async function setToken() {
try {
const token = await firebaseCloudMessaging.init();
if (token) {
getMessage();
}
} catch (error) {
console.log(error);
}
}function getMessage() {
const messaging = firebase.messaging();
messaging.onMessage((message) => console.log(‘foreground ’, message));
}}, []);return <div></div>;
}export default App;
Test FCM Notification with POSTMAN
Firebase allows us to pass messages from our app server to client apps via FCM.
https://fcm.googleapis.com/fcm/send
All we need is an FCM server token and FCM token which we will getting from the webPush.js file that we created before.
Go to Firebase console > Project Settings > Cloud Messaging
and copy server token.
Open Postman > import > Paste Raw Text
and paste the following curl and new request will be created
Replace {{FCM server token}} with the server token which we copied from the firebase console and {{FCM token}} with the token received from the webPush.js file
curl --location --request POST 'https://fcm.googleapis.com/fcm/send' \
--header 'Content-Type: application/json' \
--header 'Authorization: key={{FCM server token}}' \
--header 'Content-Type: text/plain' \
--data-raw '
{
"to" : "{{FCM token}}",
"data": {
"notification": {
"title": "Hello",
"body": "world"
}
}'
Once you finish setting up the FCM server token in header and FCM token in post body, press Send to send the notification.
If the app is open and active in the browser window you will be receiving the notification in the console tab of developer options
messaging.onMessage((message) => console.log(‘foreground ’, message));
If the app is in the background you will receive a popup out of nowhere, which will be there for sometime and then fades away. This can be triggered in firebase-messaging-sw.js service worker file.
firebase.messaging().setBackgroundMessageHandler((payload) => console.log(‘payload’, payload));