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:
Web Push Notifications: Delivered through web browsers on desktop or mobile devices
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 ProjectFirst, create a project in the Firebase Console and obtain your configuration details.
Step 2: Install Firebase SDKnpm install firebase
# or
yarn add firebase
Step 3: Create Firebase ConfigurationCreate 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 WorkerFor 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 TokenCreate 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 MessagesTo 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 NotificationsCreate 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 AppSign up for OneSignal and create a new app in their dashboard.
Step 2: Install OneSignal SDKnpm 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 InstanceSign up for Pusher and create a Beams instance.
Step 2: Install Pusher Beams SDKnpm 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 Guide - MDN's comprehensive documentation
Firebase Cloud Messaging - Official FCM documentation
OneSignal Documentation - Guides for implementing OneSignal
Pusher Beams Documentation - Official Pusher Beams guides
Next.js Documentation - For general Next.js implementation questions