Implementing push notifications with Create React App

Vikram Thyagarajan
Founding Ithaka
Published in
4 min readApr 16, 2018

One of the benefits of CRA and React is the speed/performance it provides. One of the greatest ways it does so is using Service Workers to cache all static files when deployed in production. This means that every subsequent request to the app resolves much quicker and makes the app seem much smoother.

However at times we want more from our apps. We want to be able to do things like send push notifications to further engage and bring back our users. However there is no out of the box support for extending the default service worker that CRA provides. Here we’ll go over some ways we can do so ourselves. Be warned that each of these ways requires one to eject (yarn eject) from the default CRA configuration.

Step 1: Understanding the default CRA service worker

Create React App uses sw-precache internally in its build process to generate the cacheable resources and save it to the app cache. It detects all the static resources and generates their hashes and stores them in the generate service-worker.js file.

It then ensures that each of these files are served cache-first. This way the most important part of our app — the shell — shows up immediately.

An important part of its functionality is also keeping the cache up to date with the latest files. How it does this is quite simple. Each subsequent build of our CRA app creates a new set of file with new hashes. Along with this a new service-worker.js file is created with these new files with the new hashes of the files

The browser then checks periodically if a new service worker is available with a different content hash. Since a new one is available, it is fetched and downloaded, along with the new files. We can link to when the “new content is available” event in the registerServiceWorker.js file

Step 2: Set up the custom service worker

Create a file called custom-service-worker.js which details what should happen when the push notification is clicked or actioned in any way. In this part we just log the actions done and if the notification is clicked, we take them to the index page and remove the notification. Here we assume notifications are sent in this format -

{
data: {
primaryKey: String, // any unique identifier
body: {
// body of the notification
}
}
}

Code -

self.addEventListener(‘notificationclose’, function (e) {
var notification = e.notification;
var data = notification.data || {};
var primaryKey = data.primaryKey;
console.debug(‘Closed notification: ‘ + primaryKey);
});
self.addEventListener(‘notificationclick’, function(e) {
var notification = e.notification;
var data = notification.data || {};
var primaryKey = data.primaryKey;
var action = e.action;
console.debug(‘Clicked notification: ‘ + primaryKey);
if (action === ‘close’) {
console.debug(‘Notification clicked and closed’, primaryKey);
notification.close();
}
else {
console.debug(‘Notification actioned’, primaryKey);
clients.openWindow('/');
notification.close();
}
});

Step 3: Add it to the CRA build process

The sw-precache plugin allows for a set of options to customize the service worker. The one that we will use is the importScripts option, which accepts an array of scripts which will be linked in the generated service worker.

To make this change, find the related config in webpack.config.prod.js file in the config folder. There in the SWPrecacheWebpackPlugin options, add the option, linking to wherever your file resides.

importScripts: [publicUrl + ‘/custom-service-worker.js’]

However, this presents a problem: Our service worker cannot be debugged in a dev environment. The sw-precache plugin is only used in production (understandably, because caching could present problems during development)

In order to have only our custom service worker run in development, we have to make a few changes to registerServiceWorker.js

In the default register function, we change the

if (process.env.NODE_ENV === ‘production’ && ‘serviceWorker’ in navigator) {
return new Promise((resolve, reject) => {
window.addEventListener(‘load’, () => {
const swUrl = ‘/service-worker.js’;
// Rest of the code follows

to

if (‘serviceWorker’ in navigator) {
return new Promise((resolve, reject) => {
window.addEventListener(‘load’, () => {
const swUrl = process.env.NODE_ENV === ‘production’? ‘/service-worker.js’: ‘/custom-service-worker.js’;
// Rest of the code follows

This makes sure our service worker is loaded in dev mode as well

Step 4: Implement the push functionality

The actual push functionality is a little too large to fit into this tutorial. Continuing with this link should help with its implementation. Just remember any code related to the service worker should go to custom-service-worker.js

Drawbacks

This approach does have a few major drawbacks-

  • Any updates to custom-service-worker.js does not cause a new build of service-worker.js. This is because none of the hashes in the generated file change and the importScripts are not appended to the file directly(they are only linked). So the content hash of `service-worker.js` remains the same, so the browser does not update its service worker
  • custom-service-worker.js is not part of the build process, so none of the linting and transpiling goodness is available for it

Wrapping up

Create React App holds sensible defaults for most new age apps, but some requirements for Progressive Web Apps require customizations. There is a ton more that can be done with Service Workers, and in a future blog post, I can cover how to get over the drawbacks that this process present.

--

--

Vikram Thyagarajan
Founding Ithaka

Software Developer with clients like PepsiCo, Unilever and McKinsey & Co. Passionate about Technology, Travel and Music. Speaks of self in 3rd person.