Web Push Notifications using Service Workers and Redirecting to a Specific Page

In today’s hyper-competitive digital landscape, businesses and individuals are constantly seeking innovative ways to engage with their audience and drive conversions. Leveraging Service Workers to deliver these notifications offers businesses a direct and efficient means to communicate with users, even when they’re not actively browsing the website.

This technology enables real-time updates, personalized messages, and targeted promotions to be delivered directly to users’ devices, ensuring timely engagement and maximizing brand visibility.

But how do you actually use Service Workers? This article will briefly cover some sample code I’ve put together using Node.js and Express that allows you to send a notification with a custom title, message and URL that the user will be redirected to when they click or tap the notification.

The Basics

The question of how to create basic notifications using almost any language and configuration that you require has been covered extensively quite a few times so I am going to skip over some of the basic setup. If you’d like a step-by-step using Node.js then look no further than Weerayut Teja’s excellent blog post on the subject.

What I will cover is expanding on Weerayut and others articles to show you exactly how to respond to clicks on those notifications and to open a page of your choice.

Subscribe Route

So first things first, most examples online will show creating a payload containing a title and a body, this will look something like the code below –

const payload = JSON.stringify({ title: "Hello World", body: "This is your first push notification" });
webpush.sendNotification(subscription, payload).catch(console.log);

Now there’s nothing wrong with this code, but for our example let’s include some additional data so we know exactly where to send the user, which user to send the notification to and a few other bits (some of this may not be relevant to your own uses).

So the code above may become something like below –

// Create the notification data
var payload = JSON.stringify({
	title: 'Example Notification',
	body: 'Body message shown in Notification',
	icon: 'https://example.site/icon-white.ico?v=1.0.1', // The icon to display on the left side of the Notification
	url: 'https://example.site/',  // The base URL to direct the user upon click
	page: clickUrl || '', // A specific location to send the user, EG. contacts
	userId: userId // A reference to an individual user, will entirely depend on your application, for me it's a unique user ID integer
})

This gives us a ton of information that we can then pass through to our Notification Workers using code similar to below –

// Push the notification to the given subscription
await this.webPush
	.sendNotification(subscription, payload)
	.catch(err => {
		if(err.statusCode && err.statusCode == 410) {
			// Subscription no longer exists
			this.notificationsModel.removeSubscription(subscription)
			return;
		}
		console.error(`Failed to send notification`)
		console.error(err)
		return false;
	})

The code above is quite straightforward, it uses the webPush API to send a notification containing the users Subscription and a payload, which is what we’ve just created further above. It has some additional checks that catch if the notification server responded with an error for any reason and then a call to ‘removeSubscription’ so that we don’t keep calling that endpoint, in my case this just removes a record from my database that contained the subscription details.

Notifications Worker

So by this point we’ve altered the payload we’re creating to add some additional information and we’ve hopefully followed another tutorial so that we’ve got a basic Notification Register configured (subscribing the user to notifications).

Assuming that’s the case the next custom bit we’ll want to modify is the Notifications Worker, this is where the cool stuff happens. We’ll ingest the payload we created earlier and show a Notification to the user and also respond to a click event so that the user is automatically navigated to a page of our choosing.

Following Weerayut Teja’s excellent blog post on the subject again, let’s take his example Service Worker as shown below and expand on it to show additional information in the Notification and to add a notificationclick handler that will respond to a user clicking/tapping that notification –

self.addEventListener('push', function(e) {
    const data = e.data.json();
    self.registration.showNotification(
        data.title,
        {
            body: data.body,
        }
    );
})

We’re going to add quite a bit here, which I’ve heavily commented –

self.addEventListener("push", event => {
	// Retrieve the JSON payload from the calling notifications controller
    const data = event.data.json();

	// Ensure the browser doesn't kill the Service Worker until the notification is shown
	event.waitUntil(
		// Show a notification
		self.registration.showNotification(
			data.title, // title of the notification
			{
				body: data.body,
				icon: data.icon,
				data: { // Additional data that we want to use later in the notificationclick handler
					url: data.url,
					page: data.page
				}
			}
		)
	)
});

// Handle notification clicks/taps
self.addEventListener('notificationclick', event => {
	// Close the notification
	event.notification.close()

	// Ensure the browser doesn't kill the Service Worker until the notification is tapped
	event.waitUntil(
		// Return all service workers clients
		clients.matchAll().then(matchedClients => {
			// If one matches the URL we want to send the user to, focus on that instead of creating a new one
			for (let client of matchedClients) {
				if (client.url === event.notification.data.url) {
					return client.focus();
				}
			}

			// Otherwise, open a new window using the URL we passed through
			return clients
				.openWindow(event.notification.data.url)
				.then(client => { 
					// Once the window is opened, navigate to the specific page that we want to then focus on it
					client.navigate(event.notification.data.page)
					client.focus();
				})
				.catch(err => console.error('Failed to open window:', err));
		})
	)
})

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.