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.

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author