PWA update notifications in a React app
PWA update notifications in a React app
PWA update notifications in a React app
Discover how we built the system behind PWA update notifications in a React app at Toplyne.
Discover how we built the system behind PWA update notifications in a React app at Toplyne.
Discover how we built the system behind PWA update notifications in a React app at Toplyne.
Ideally, you would always want users to use the latest version of your application. Having an interactive experience that speaks to the user when you push a new feature or make a new release or fix a bug through visual feedback: there is a new update… please refresh to view the fresh changes: is what our requirements were at ツ Toplyne. I’ll walk you through my experience of building such a system.
What does the end goal look like?
The requirements are:
Check if a new update is available for the app, show a notification saying “A new version of the app is available, REFRESH?”
When the user navigates, check if there is a new version and manually update the page (this is a rather opinionated approach which works perfectly as per our use case, we do not surprise users with a reload, we show the above notification first, if users do not refresh themselves, we do it for them on a subsequent page navigation). This is pretty easy to do once the previous requirement is in place.
We did leverage the PWA (Progressive Web App) feature that comes with a React app. Using this, we get to use the superpowers of a service worker. Some notable features of service workers are:
runs in the background independent of main js thread
can be used to cache static assets and network requests
can be paired with workbox to make your app run in offline mode
In our case, we utilized a service worker to check for updates periodically and made use of the service worker’s update event to show notification and/or update the app. You can read more about the service worker life cycle here. Learning about the lifecycle is crucial in understanding the what, when, and why, and how to override the default behavior.
image credits: hasura.io
Enough talk, show me the code already
We had disabled the default service worker that comes with CRA, so we had to add it manually. Adding this boilerplate is quite straight forward. Use the cra-template-pwa and copy whatever you need. Generally, it’s the workbox dependencies in package.json, service-worker.js, serviceWorkerRegistration.js.
These files contain the basic logic for registering a service worker in your browser. Ideally, you’d import the serviceWorker from serviceWorkerRegistration.js and call the register method on it in a suitable location/component. By default, it is index.js, you can move it to wherever you like but the component should always render.
For my use case, I moved it to App.tsx to make use of React state and effects which would provide helpers to solve our use cases.
Checking for updates and showing a notification
This is our first task. We need to periodically check for updates and if there is an update found, show the notification to refresh the page.
The workflow is:
poll periodically to check for sw (short for service worker) updates by calling sw’s update() function. Update the default service worker registeration in serviceWorkerRegistration.js file. This happens in a separate thread and is non blocking to the main js thread, so calling setInterval() should be okay.
When we call the update() method, it will compare new and older versions and if they are even a little bit different, it installs the new worker on the browser. When a new worker is found, the default behavior is to not replace the old worker straight away, this is done for obvious reasons so that user interaction doesn't get ruined. The new worker goes into a forever waiting phase until the old worker is invalidated (usually 24 hours/ hard refresh/ closing tabs and opening).
We need to manually tell it to skip_waiting and take control… We do this by adding a custom event listener to the service worker to listen for custom messages. Add the following at the end of service-worker.js file.
This step is very important because it allows us to hook custom handlers to control the lifecycle of a service worker according to our needs.
Putting everything in order, showing the update notification
I’ve written a custom hook 🚀 useServiceWorker.ts which initializes the service worker registration and exposes functions to control the visibility of our notification alert.
When a service worker update is found, we store it as a waitingWorker. This gives us the control over calling SKIP_WAITING manually whenever we need to (in our case it's on clicking of REFRESH button on the update notification).
How and where to show the notification
I’ve used this custom hook in my App entrypoint App.tsx. You can use it wherever you want to, but the component should always render and better be a parent to all your child components.
and that’s it…..
Considerations
There are a lot of things you need to take care of when you’re testing a service worker deployment. Deploying a buggy service worker can ruin your app experience and these things are very hard to get rid of since they exist on the client’s browser and take a long time to get invalidated. So consider the following points before deploying your new feature:
sw gets enabled on the production build of react, so it is useless to test it on dev. You wouldn’t want to test on dev as it leads to issues with hot reload and default caching mechanisms there.
service worker can cache your static assets, you don’t need to add cache rules for js, css, images in your deployment service manually.
(Very Important!!) Make sure not to cache your sw on your deployment service, else your new sw will never get installed, and you might never see a notification. Make sure your service-worker.js file has Cache-Control headers set to max-age=0,no-cache,no-store,must-revalidate. Read more about these headers here.
create-react-app includes a service worker by default and makes your app work offline by default.
(Very Important!) if you do run into issues with deployed service workers, push a release with calling unregister() to the sw and restore Cache-Control headers.
You sometimes may get an error in production that says “Failed to update a ServiceWorker for scope (‘{{hostname}}/’) with script (‘{{hostname}}/service-worker.js’): An unknown error occurred when fetching the script.”. This happens when you push a release and at the moment when your app is building, your sw checks for an update. This is a harmless thing and your app works fine on the next update check. (We’re currently figuring out what you can do to gracefully handle this error).
A lot of apps use service workers nowadays and it acts as a very useful tool if used correctly. With great power comes great responsibility. Peace out! ✌️
Working at Toplyne
We’re always looking for talented engineers to join our team. You can find and apply for relevant roles here.
Ideally, you would always want users to use the latest version of your application. Having an interactive experience that speaks to the user when you push a new feature or make a new release or fix a bug through visual feedback: there is a new update… please refresh to view the fresh changes: is what our requirements were at ツ Toplyne. I’ll walk you through my experience of building such a system.
What does the end goal look like?
The requirements are:
Check if a new update is available for the app, show a notification saying “A new version of the app is available, REFRESH?”
When the user navigates, check if there is a new version and manually update the page (this is a rather opinionated approach which works perfectly as per our use case, we do not surprise users with a reload, we show the above notification first, if users do not refresh themselves, we do it for them on a subsequent page navigation). This is pretty easy to do once the previous requirement is in place.
We did leverage the PWA (Progressive Web App) feature that comes with a React app. Using this, we get to use the superpowers of a service worker. Some notable features of service workers are:
runs in the background independent of main js thread
can be used to cache static assets and network requests
can be paired with workbox to make your app run in offline mode
In our case, we utilized a service worker to check for updates periodically and made use of the service worker’s update event to show notification and/or update the app. You can read more about the service worker life cycle here. Learning about the lifecycle is crucial in understanding the what, when, and why, and how to override the default behavior.
image credits: hasura.io
Enough talk, show me the code already
We had disabled the default service worker that comes with CRA, so we had to add it manually. Adding this boilerplate is quite straight forward. Use the cra-template-pwa and copy whatever you need. Generally, it’s the workbox dependencies in package.json, service-worker.js, serviceWorkerRegistration.js.
These files contain the basic logic for registering a service worker in your browser. Ideally, you’d import the serviceWorker from serviceWorkerRegistration.js and call the register method on it in a suitable location/component. By default, it is index.js, you can move it to wherever you like but the component should always render.
For my use case, I moved it to App.tsx to make use of React state and effects which would provide helpers to solve our use cases.
Checking for updates and showing a notification
This is our first task. We need to periodically check for updates and if there is an update found, show the notification to refresh the page.
The workflow is:
poll periodically to check for sw (short for service worker) updates by calling sw’s update() function. Update the default service worker registeration in serviceWorkerRegistration.js file. This happens in a separate thread and is non blocking to the main js thread, so calling setInterval() should be okay.
When we call the update() method, it will compare new and older versions and if they are even a little bit different, it installs the new worker on the browser. When a new worker is found, the default behavior is to not replace the old worker straight away, this is done for obvious reasons so that user interaction doesn't get ruined. The new worker goes into a forever waiting phase until the old worker is invalidated (usually 24 hours/ hard refresh/ closing tabs and opening).
We need to manually tell it to skip_waiting and take control… We do this by adding a custom event listener to the service worker to listen for custom messages. Add the following at the end of service-worker.js file.
This step is very important because it allows us to hook custom handlers to control the lifecycle of a service worker according to our needs.
Putting everything in order, showing the update notification
I’ve written a custom hook 🚀 useServiceWorker.ts which initializes the service worker registration and exposes functions to control the visibility of our notification alert.
When a service worker update is found, we store it as a waitingWorker. This gives us the control over calling SKIP_WAITING manually whenever we need to (in our case it's on clicking of REFRESH button on the update notification).
How and where to show the notification
I’ve used this custom hook in my App entrypoint App.tsx. You can use it wherever you want to, but the component should always render and better be a parent to all your child components.
and that’s it…..
Considerations
There are a lot of things you need to take care of when you’re testing a service worker deployment. Deploying a buggy service worker can ruin your app experience and these things are very hard to get rid of since they exist on the client’s browser and take a long time to get invalidated. So consider the following points before deploying your new feature:
sw gets enabled on the production build of react, so it is useless to test it on dev. You wouldn’t want to test on dev as it leads to issues with hot reload and default caching mechanisms there.
service worker can cache your static assets, you don’t need to add cache rules for js, css, images in your deployment service manually.
(Very Important!!) Make sure not to cache your sw on your deployment service, else your new sw will never get installed, and you might never see a notification. Make sure your service-worker.js file has Cache-Control headers set to max-age=0,no-cache,no-store,must-revalidate. Read more about these headers here.
create-react-app includes a service worker by default and makes your app work offline by default.
(Very Important!) if you do run into issues with deployed service workers, push a release with calling unregister() to the sw and restore Cache-Control headers.
You sometimes may get an error in production that says “Failed to update a ServiceWorker for scope (‘{{hostname}}/’) with script (‘{{hostname}}/service-worker.js’): An unknown error occurred when fetching the script.”. This happens when you push a release and at the moment when your app is building, your sw checks for an update. This is a harmless thing and your app works fine on the next update check. (We’re currently figuring out what you can do to gracefully handle this error).
A lot of apps use service workers nowadays and it acts as a very useful tool if used correctly. With great power comes great responsibility. Peace out! ✌️
Working at Toplyne
We’re always looking for talented engineers to join our team. You can find and apply for relevant roles here.
Related Articles
Behavioral Retargeting: A Game-Changer in the Cookieless Era
Unlock the power of behavioral retargeting for the cookieless future! Learn how it personalizes ads & boosts conversions. #behavioralretargeting
All of Toplyne's 40+ Badges in the G2 Spring Reports
Our customers awarded us 40+ badges in G2's Summer Report 2024.
Unlocking the Full Potential of Google PMax Campaigns: Mastering Audience Selection to Double Your ROAS
Copyright © Toplyne Labs PTE Ltd. 2024
Copyright © Toplyne Labs PTE Ltd. 2024
Copyright © Toplyne Labs PTE Ltd. 2024
Copyright © Toplyne Labs PTE Ltd. 2024