How to Implement Push Notifications on a Next.js App

You've built a sleek Next.js app with impressive features and smooth performance. But now you're faced with a critical challenge: your users aren't engaging with your content once they leave your site. You need a way to bring them back, to notify them of important updates, new features, or time-sensitive information—even when they're not actively using your application.

Push notifications are the answer, but implementing them in a Next.js app can feel overwhelming. Between browser compatibility issues, Apple's costly developer requirements, and the technical complexity of setting up notification systems, many developers are left frustrated and unsure where to start.

Understanding Push Notifications

Push notifications are small messages sent from a web application to users' devices, viewable even when users aren't actively engaging with your app. According to OneSignal, these notifications appear on a user's device without requiring the application to be open, making them a powerful tool for re-engagement.

There are two primary types of push notifications:

  1. Web Push Notifications: Delivered through web browsers on desktop or mobile devices

  2. Mobile Push Notifications: Sent through mobile applications installed on devices

Key Push Notification Services and APIs

To implement push notifications, you'll need to understand the major services available:

  • Firebase Cloud Messaging (FCM): Google's free service for sending notifications to both web and mobile applications

  • Apple Push Notification Service (APNs): Required for sending notifications to iOS devices (requires a paid Apple Developer account)

  • Web Push API: The standard browser API that enables web applications to receive push messages

Each of these services has specific implementation requirements and limitations, which we'll explore throughout this article.

Benefits of Implementing Push Notifications

Before diving into implementation details, it's important to understand why push notifications are worth the effort:

  • Increased User Engagement: Push notifications can achieve open rates of around 20%, compared to just 2% for emails

  • Improved User Retention: Regular, valuable notifications keep your app top-of-mind

  • Time-Sensitive Updates: Deliver critical information immediately

  • Targeted Communication: Segment users and deliver personalized notifications

According to a survey by Localytics, users in 2017 were actually more willing to receive push notifications than users in 2015 before disabling them—showing growing acceptance of this communication channel when used properly.

Implementing Push Notifications in Next.js: Different Approaches

Let's explore several approaches to implementing push notifications in a Next.js application, from building your own solution to using third-party services.

Approach 1: Building with Firebase Cloud Messaging (FCM)

Firebase Cloud Messaging offers a robust, free solution for implementing push notifications. Here's how to set it up in your Next.js application:

Step 1: Set Up Firebase Project

First, create a project in the Firebase Console and obtain your configuration details.

Step 2: Install Firebase SDK
npm install firebase
# or
yarn add firebase
Step 3: Create Firebase Configuration

Create a firebase.js file in your project:

// firebase.js
import { initializeApp } from 'firebase/app';
import { getMessaging, getToken, onMessage } from 'firebase/messaging';

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"
};

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

// Initialize Firebase Cloud Messaging
let messaging;

// We need to check if we're in the browser environment
if (typeof window !== 'undefined') {
  messaging = getMessaging(app);
}

export { messaging, getToken, onMessage };
Step 4: Create a Service Worker

For FCM to work, you need a service worker. Create a file called firebase-messaging-sw.js in your public folder:

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

firebase.initializeApp({
  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"
});

const messaging = firebase.messaging();

// Handle background messages
messaging.onBackgroundMessage(function(payload) {
  console.log('Received background message ', payload);

  const notificationTitle = payload.notification.title;
  const notificationOptions = {
    body: payload.notification.body,
    icon: '/favicon.ico'
  };

  self.registration.showNotification(notificationTitle, notificationOptions);
});
Step 5: Request Permission and Get Token

Create a component to request notification permissions:

// components/NotificationPermission.jsx
import { useEffect, useState } from 'react';
import { messaging, getToken } from '../firebase';

export default function NotificationPermission() {
  const [token, setToken] = useState('');
  const [notificationPermission, setNotificationPermission] = useState('default');

  useEffect(() => {
    if (typeof window !== 'undefined' && messaging) {
      // Request permission
      Notification.requestPermission().then((permission) => {
        setNotificationPermission(permission);
        
        if (permission === 'granted') {
          // Get FCM token
          getToken(messaging, { 
            vapidKey: "YOUR_VAPID_KEY" 
          }).then((currentToken) => {
            if (currentToken) {
              // Send the token to your server
              console.log('FCM Token:', currentToken);
              setToken(currentToken);
              
              // Here you would typically send this token to your backend
              sendTokenToServer(currentToken);
            } else {
              console.log('No registration token available.');
            }
          }).catch((err) => {
            console.log('An error occurred while retrieving token. ', err);
          });
        }
      });
    }
  }, []);

  const sendTokenToServer = async (token) => {
    try {
      const response = await fetch('/api/registerPushToken', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token })
      });
      const data = await response.json();
      console.log('Token registered:', data);
    } catch (error) {
      console.error('Error registering token:', error);
    }
  };

  return (
    <div>
      {notificationPermission === 'granted' ? (
        <p>Thank you for enabling notifications!</p>
      ) : (
        <button onClick={() => Notification.requestPermission()}>
          Enable Push Notifications
        </button>
      )}
    </div>
  );
}
Step 6: Handle Foreground Messages

To receive notifications when your app is open, set up a handler:

// In your _app.js or a component that's always mounted
import { useEffect } from 'react';
import { messaging, onMessage } from '../firebase';

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    if (typeof window !== 'undefined' && messaging) {
      // Handle messages when the app is in the foreground
      const unsubscribe = onMessage(messaging, (payload) => {
        console.log('Message received in foreground:', payload);
        // You can show a custom notification UI here
        // Or use the browser's notification API
        new Notification(payload.notification.title, {
          body: payload.notification.body,
          icon: '/favicon.ico'
        });
      });
      
      return () => unsubscribe();
    }
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;
Step 7: Set Up API Endpoint to Send Notifications

Create an API endpoint in your Next.js app to send notifications:

// pages/api/sendNotification.js
import admin from 'firebase-admin';

// Initialize Firebase Admin if it hasn't been initialized yet
if (!admin.apps.length) {
  admin.initializeApp({
    credential: admin.credential.cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
    }),
  });
}

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { token, title, body } = req.body;

  try {
    const message = {
      notification: {
        title,
        body,
      },
      token,
    };

    const response = await admin.messaging().send(message);
    return res.status(200).json({ success: true, response });
  } catch (error) {
    console.error('Error sending notification:', error);
    return res.status(500).json({ error: error.message });
  }
}

Approach 2: Using OneSignal

OneSignal is a popular third-party service that simplifies push notification implementation. It's particularly well-suited for e-commerce applications and provides a generous free tier.

Step 1: Create a OneSignal Account and App

Sign up for OneSignal and create a new app in their dashboard.

Step 2: Install OneSignal SDK
npm install react-onesignal
# or
yarn add react-onesignal
Step 3: Initialize OneSignal in Your Next.js App
// pages/_app.js
import { useEffect } from 'react';
import OneSignal from 'react-onesignal';

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    OneSignal.init({
      appId: "YOUR_ONESIGNAL_APP_ID",
      notifyButton: {
        enable: true,
      },
      allowLocalhostAsSecureOrigin: true,
    });
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;
Step 4: Create API Route for Sending Notifications
// pages/api/sendOneSignalNotification.js
export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { segments, heading, content } = req.body;

  try {
    const response = await fetch('https://onesignal.com/api/v1/notifications', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Basic ${process.env.ONESIGNAL_REST_API_KEY}`,
      },
      body: JSON.stringify({
        app_id: process.env.ONESIGNAL_APP_ID,
        included_segments: segments || ['All'],
        headings: { en: heading },
        contents: { en: content },
      }),
    });

    const data = await response.json();
    return res.status(200).json(data);
  } catch (error) {
    console.error('Error sending OneSignal notification:', error);
    return res.status(500).json({ error: error.message });
  }
}

Approach 3: Using Pusher Beams

Pusher Beams is another excellent service for implementing push notifications with a focus on real-time capabilities.

Step 1: Set Up Pusher Account and Beams Instance

Sign up for Pusher and create a Beams instance.

Step 2: Install Pusher Beams SDK
npm install @pusher/beams-client
# or
yarn add @pusher/beams-client
Step 3: Create a Beams Context Provider
// contexts/BeamsContext.jsx
import { createContext, useContext, useEffect, useState } from 'react';
import * as PusherPushNotifications from '@pusher/beams-client';

const BeamsContext = createContext();

export function BeamsProvider({ children }) {
  const [beamsClient, setBeamsClient] = useState(null);
  const [userId, setUserId] = useState(null);

  useEffect(() => {
    if (typeof window !== 'undefined') {
      const client = new PusherPushNotifications.Client({
        instanceId: 'YOUR_INSTANCE_ID',
      });

      client.start()
        .then(() => client.addDeviceInterest('hello'))
        .then(() => {
          console.log('Successfully registered and subscribed!');
          setBeamsClient(client);
        })
        .catch(console.error);
    }
  }, []);

  const subscribeToInterest = (interest) => {
    if (beamsClient) {
      beamsClient.addDeviceInterest(interest)
        .then(() => console.log(`Subscribed to ${interest}`))
        .catch(console.error);
    }
  };

  const unsubscribeFromInterest = (interest) => {
    if (beamsClient) {
      beamsClient.removeDeviceInterest(interest)
        .then(() => console.log(`Unsubscribed from ${interest}`))
        .catch(console.error);
    }
  };

  return (
    <BeamsContext.Provider 
      value={{ 
        beamsClient, 
        subscribeToInterest, 
        unsubscribeFromInterest 
      }}
    >
      {children}
    </BeamsContext.Provider>
  );
}

export const useBeams = () => useContext(BeamsContext);
Step 4: Wrap Your App with the Provider
// pages/_app.js
import { BeamsProvider } from '../contexts/BeamsContext';

function MyApp({ Component, pageProps }) {
  return (
    <BeamsProvider>
      <Component {...pageProps} />
    </BeamsProvider>
  );
}

export default MyApp;
Step 5: Create API Route for Sending Notifications
// pages/api/sendBeamsNotification.js
import Pusher from '@pusher/push-notifications-server';

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const { interests, title, body } = req.body;

  try {
    const beamsClient = new Pusher({
      instanceId: process.env.PUSHER_INSTANCE_ID,
      secretKey: process.env.PUSHER_SECRET_KEY,
    });

    const response = await beamsClient.publishToInterests(interests, {
      web: {
        notification: {
          title,
          body,
        },
      },
    });

    return res.status(200).json(response);
  } catch (error) {
    console.error('Error sending Pusher Beams notification:', error);
    return res.status(500).json({ error: error.message });
  }
}

Comparing Push Notification Solutions

Let's compare these approaches to help you choose the right solution for your Next.js application:

Firebase Cloud Messaging (FCM)

Pros:

  • Free to use with generous quotas

  • Excellent integration with other Firebase services

  • Good documentation and community support

  • Works well for both web and mobile applications

  • Direct integration with Google services

Cons:

  • Complex setup process

  • Requires maintaining a service worker

  • Limited customization for notification appearance

Best for: Applications that already use Firebase services or need a free solution with good scalability.

OneSignal

Pros:

  • Easy to set up with minimal configuration

  • Excellent dashboard for campaign management

  • Great analytics and segmentation capabilities

  • Handles browser compatibility issues for you

  • Free tier available with generous limits

Cons:

  • Paid plans can get expensive at scale

  • Less control over the technical implementation

  • Adds third-party dependencies to your app

Best for: E-commerce businesses and applications that need quick implementation with advanced analytics and segmentation features.

Pusher Beams

Pros:

  • Simple API for real-time notifications

  • Good documentation and easy integration

  • Reliable delivery system

  • Works well with other Pusher services

Cons:

  • Limited free tier

  • Not as feature-rich as OneSignal for marketing purposes

  • Less mature than FCM

Best for: Applications already using Pusher services or needing real-time capabilities with simple implementation.

Browser Compatibility and Challenges

When implementing push notifications, be aware of these common challenges:

Safari Support and Apple Developer Program

The most significant hurdle many developers face is supporting Safari. As one developer noted on Reddit:

"In order to send notifications to Safari, you need to sign up to Apple's yearly developer plan which costs money."

This requirement for a paid Apple Developer account ($99/year) is a significant barrier for many projects, especially smaller ones or those in early development stages.

User Perception and Acceptance

Another challenge is user perception. Many users instinctively decline web push notification requests:

"When a website asks me if I want to allow notifications, I don't even think about it, I just say no. But if a mobile app asks, well, I usually give it a chance before turning them off."

This highlights the importance of timing your permission request and explaining the value of notifications before requesting permission.

Technical Complexity

Push notification systems are intrinsically complex:

"Push ALWAYS causes issues. Between being generally unreliable and being hard to implement it's just a pain."

This complexity makes third-party services particularly attractive for many developers, especially for e-commerce applications:

"If your business is an e-commerce I would pick a service, building from scratch would be too much work."

Best Practices for Push Notifications in Next.js

To maximize the effectiveness of your push notifications:

1. Request Permission at the Right Time

Don't ask for permission immediately when a user visits your site. Wait until they've engaged with your content and understand the value your notifications will provide.

2. Clearly Communicate the Value

Explain what types of notifications you'll send and how they'll benefit the user before requesting permission.

3. Personalize and Segment

Use user data to send relevant notifications rather than generic messages to everyone.

4. Respect Frequency and Timing

Don't overwhelm users with too many notifications. Be mindful of time zones and send notifications at appropriate times.

5. Make Opt-Out Easy

Always provide a clear way for users to unsubscribe or manage their notification preferences.

Conclusion

Implementing push notifications in a Next.js application can significantly enhance user engagement and retention. While there are technical challenges, particularly around browser compatibility and user acceptance, the benefits often outweigh the complexities.

For most developers, especially those working on e-commerce applications or without specific push notification expertise, using a third-party service like OneSignal or Pusher Beams will provide the quickest path to implementation with the least technical overhead.

For those already using Firebase or needing more control over the implementation, FCM offers a robust, free solution that integrates well with other services.

Whichever approach you choose, remember to respect your users' attention and provide genuine value through your notifications. This will ensure they remain opted in and engaged with your application over the long term.

Additional Resources

Web Push Notifications

6/19/2025
Related Posts
How to Handle Authentication Across Separate Backend and Frontend for Next.js Website

How to Handle Authentication Across Separate Backend and Frontend for Next.js Website

Learn how to implement secure authentication in Next.js with Express backend using httpOnly cookies, JWT tokens, and middleware. Complete guide with code examples.

Read Full Story
Auth.js vs BetterAuth for Next.js: A Comprehensive Comparison

Auth.js vs BetterAuth for Next.js: A Comprehensive Comparison

Comprehensive comparison of Auth.js vs BetterAuth for Next.js, covering setup, documentation, support, and implementation. Make the right choice for your authentication needs.

Read Full Story
Optimizing Your tRPC Implementation in Next.js

Optimizing Your tRPC Implementation in Next.js

Discover how to balance server and client responsibilities in your tRPC setup. Stop wrestling with performance issues and start leveraging Next.js features for blazing-fast applications.

Read Full Story