How to add push notifications in React
Push notifications allow you to re-engage users even when they’re not actively using your React application. As the creator of CoreUI with 12 years of React development experience, I’ve implemented push notification systems that serve millions of users daily.
The most reliable approach is to use the Web Push API with service workers and a backend server for sending notifications.
Install Dependencies
First, install the web-push library for your backend:
npm install web-push
For your React frontend, you’ll need a service worker (see how to add service workers in React if you haven’t set this up yet).
Generate VAPID Keys
VAPID keys authenticate your push notifications. Generate them once:
npx web-push generate-vapid-keys
Save the public and private keys in your environment variables:
REACT_APP_VAPID_PUBLIC_KEY=your_public_key_here
VAPID_PRIVATE_KEY=your_private_key_here
Request Notification Permission
Create a notification service in src/services/notificationService.js:
const urlBase64ToUint8Array = (base64String) => {
const padding = '='.repeat((4 - base64String.length % 4) % 4)
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/')
const rawData = window.atob(base64)
const outputArray = new Uint8Array(rawData.length)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
export const requestNotificationPermission = async () => {
if (!('Notification' in window)) {
throw new Error('This browser does not support notifications')
}
const permission = await Notification.requestPermission()
if (permission !== 'granted') {
throw new Error('Notification permission denied')
}
return permission
}
export const subscribeUserToPush = async () => {
if (!('serviceWorker' in navigator)) {
throw new Error('Service workers are not supported')
}
const registration = await navigator.serviceWorker.ready
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(
process.env.REACT_APP_VAPID_PUBLIC_KEY
)
})
return subscription
}
export const sendSubscriptionToServer = async (subscription) => {
const response = await fetch('/api/notifications/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(subscription)
})
if (!response.ok) {
throw new Error('Failed to save subscription')
}
return response.json()
}
Update Service Worker
Update your public/service-worker.js to handle push events:
self.addEventListener('push', (event) => {
const data = event.data.json()
const options = {
body: data.body,
icon: '/logo192.png',
badge: '/badge.png',
data: {
url: data.url || '/'
},
actions: [
{
action: 'open',
title: 'Open'
},
{
action: 'close',
title: 'Close'
}
]
}
event.waitUntil(
self.registration.showNotification(data.title, options)
)
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
if (event.action === 'open' || !event.action) {
const url = event.notification.data.url
event.waitUntil(
clients.openWindow(url)
)
}
})
Use in React Component
Create a component to handle push notification subscription:
import React, { useState, useEffect } from 'react'
import {
requestNotificationPermission,
subscribeUserToPush,
sendSubscriptionToServer
} from './services/notificationService'
function NotificationSubscribe() {
const [isSubscribed, setIsSubscribed] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
checkSubscription()
}, [])
const checkSubscription = async () => {
if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.ready
const subscription = await registration.pushManager.getSubscription()
setIsSubscribed(!!subscription)
}
}
const handleSubscribe = async () => {
try {
setLoading(true)
setError(null)
await requestNotificationPermission()
const subscription = await subscribeUserToPush()
await sendSubscriptionToServer(subscription)
setIsSubscribed(true)
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
const handleUnsubscribe = async () => {
try {
setLoading(true)
const registration = await navigator.serviceWorker.ready
const subscription = await registration.pushManager.getSubscription()
if (subscription) {
await subscription.unsubscribe()
setIsSubscribed(false)
}
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}
return (
<div>
<h2>Push Notifications</h2>
{error && <div className="error">{error}</div>}
{!isSubscribed ? (
<button onClick={handleSubscribe} disabled={loading}>
{loading ? 'Subscribing...' : 'Enable Notifications'}
</button>
) : (
<button onClick={handleUnsubscribe} disabled={loading}>
{loading ? 'Unsubscribing...' : 'Disable Notifications'}
</button>
)}
</div>
)
}
export default NotificationSubscribe
Backend Server Example
Create a simple Node.js endpoint to send notifications:
const webpush = require('web-push')
webpush.setVapidDetails(
'mailto:[email protected]',
process.env.REACT_APP_VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
)
app.post('/api/notifications/send', async (req, res) => {
const { subscription, title, body, url } = req.body
const payload = JSON.stringify({
title,
body,
url
})
try {
await webpush.sendNotification(subscription, payload)
res.status(200).json({ message: 'Notification sent' })
} catch (error) {
console.error('Error sending notification:', error)
res.status(500).json({ error: 'Failed to send notification' })
}
})
Best Practice Note
This is the same push notification architecture we use in CoreUI’s React PWA templates. It provides reliable, cross-browser notification delivery with proper error handling and subscription management.
For production applications, consider using CoreUI’s React Admin Template which includes pre-built push notification support with service workers, subscription management, and backend integration examples.
Related Articles
If you’re implementing PWA features, you might also want to learn how to add service workers in React for offline support and caching capabilities.



