How to display a "new version available" for a Progressive Web App

In this article, I will show you how to add a notification to your site and display it each time that there is a new version of the service worker available. You’ll also learn how to refresh the page, so that the user is up to date and has the latest version of any cached files.

Have you ever been on a website and noticed a popup notification that suggests that there is a new version of the site available? I recently visited Google’s Inbox and noticed a notification a little like the image below.

New version available - Progressive Web App

I’ve built a number of Progressive Web Apps that simply update the service worker silently for the user in the background, but I really like this approach - especially for an offline first web app. If you’ve ever tried to build a web application that is completely offline first, you’ll know how tricky it can be to make changes to the users cache when you do make updates to the site and the user has connectivity. This is where a pop up notification like Google’s Inbox provides the user with a means of always having the latest version of cached resources. It got me wondering how I could build something a little similar and it turns out that it is a little tricker than it seems - but it is not impossible!

In this article, I will show you how to add a notification to your site and display it each time that there is a new version of the service worker available. You’ll also learn how to refresh the page, so that the user is up to date and has the latest version of any cached files. This article is a bit of a lengthy beast, so strap in tight and get comfy!

Getting started

In this example, I am going to use a very basic web page that consists of three assets:

  • index.html
  • dog.jpg
  • service-worker.js

To start off with, the HTML for my web page looks a little like the following code.

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>PWA - New Service Worker available</title>
</head>

<body>
  <img src="./dog-face.jpg" />
  <!-- The notification that will pop up -->
  <div id="notification">A new version of this app is available. Click <a id="reload">here</a> to update.</div>
</body>
<script>

 if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('./service-worker.js').then(function(registration) {
          // Registration was successful
          console.log('ServiceWorker registration successful with scope: ', registration.scope);
      }).catch(function(err) {
          // registration failed :(
            console.log('ServiceWorker registration failed: ', err);
          });
      }

</script>
</html>

In the web page above, you may notice that I’ve added standard boilerplate code for a web page and the registering of a service worker. Let’s add a bit of service worker magic! Next, create a file and call it service-worker.js and add the following code:

const cacheName = ‘firstVersion';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(cacheName)
      .then(cache => cache.addAll([
        './dog.jpg'
      ]))
  );
});

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request)
      .then(function (response) {
        if (response) {
          return response;
        }
        return fetch(event.request);
      })
  );
});

In the code above, we have added basic caching functionality to our service worker. Once the service worker has been installed and each time a user makes the request for the dog.jpg image, it will retrieve it from cache and instantly display it to the user. If you aren’t familiar with the code above, I recommend checking out this article for more information - it will take you through the basics and help you understand how service worker caching fits in.

At this point in time, if we fire up the web page it will look a little something like the image below.

Service Worker Update Screenshot

So far so good, but we have a web page that doesn’t really do a whole lot! In order to complete the pieces of the puzzle, we need to update our code so that it notifies the user when there has been a change to the service worker itself. Before we dive any deeper, let’s take a look at the basic flow that needs to take place.

New version available diagram - Progressive Web App

In the diagram above, you may notice that a number of steps need to take place before we have a finished product. Firstly, the browser checks to see if there has been an update to the service worker file. If there is an update available, then we show a notification on the screen, else we do nothing. When the user clicks on the notification, we then post a message to the service worker and tell it to skip waiting and become the active service worker. Once that is complete, we then reload the page and our new service worker is in control. Hooray!

While it may seem confusing at first, by the end of this article the above flow will make a bit more sense. Let’s take what we’ve learnt from the flow above and apply the code changes to our web page. We are going to be making the changes below to our index.html file.

  let newWorker;

  // The click event on the notification
  document.getElementById('reload').addEventListener('click', function(){
    newWorker.postMessage({ action: 'skipWaiting' });
  });

  if ('serviceWorker' in navigator) {
    // Register the service worker
    navigator.serviceWorker.register('/service-worker.js').then(reg => {
      reg.addEventListener('updatefound', () => {

        // An updated service worker has appeared in reg.installing!
        newWorker = reg.installing;

        newWorker.addEventListener('statechange', () => {

          // Has service worker state changed?
          switch (newWorker.state) {
            case 'installed':

	// There is a new service worker available, show the notification
              if (navigator.serviceWorker.controller) {
                let notification = document.getElementById('notification ');
    notification .className = 'show';
              }

              break;
          }
        });
      });
    });

  }

Woah - that code has grown a lot on our index.html page! Let’s break it down step by step in order to get a better understanding of the flow.

Once we’ve registered the service worker, we then add an eventListener to the updateFound event. This event is fired any time the ServiceWorkerRegistration.installing property acquires a new service worker. This will determine if there have been any changes to the service worker file and occurs when the user reloads or returns to the web page. The browser has a handy way of checking the contents of the service-worker.js file, and even if it has only changed by one byte, it will treat it as a new version.

If a new version is discovered, the updateFound event will be fired. If this event is fired, we then need to check if a new service worker has been acquired and assign it to a new variable as we will be using this at a later stage.

Now that we have determined that there is a new service worker waiting to be installed, we can then display a notification at the bottom of our page notifying the user that there is a new version available.

New version available notification - Progressive Web App

If you remember the diagram earlier in this article, you’ll remember that we still need to complete steps 3 & 4 in order for our new service worker to start controlling the page. For step 3, we need to add functionality to the popup notification, so that when the user clicks update, we send a postMessage() to our service worker and tell it to skip the waiting phase. Remember that you can’t communicate directly with a service worker from the client, we need to use the postMessage() method to send a message to a client (a Window, Worker, or SharedWorker). The message is received in the "message" event on navigator.serviceWorker.

The code inside the service-worker.js file should be updated to respond to the message event.

self.addEventListener('message', function (event) {
  if (event.data.action === 'skipWaiting') {
    self.skipWaiting();
  }
});

We are almost there! In our fourth and final step, we need our web page to reload and activate the new service worker. To do this, we need to update the index.html page and reload the page as soon as the controllerchange event is fired.

   let refreshing;
   // The event listener that is fired when the service worker updates
   // Here we reload the page
    navigator.serviceWorker.addEventListener('controllerchange', function () {
      if (refreshing) return;
      window.location.reload();
      refreshing = true;
    });

That’s it - you now have a fully working example!

The result

In order to test this in action, I fired this up against my localhost and navigated to the index.html page. I find it really easy to test my service worker code using Google Chrome and it’s Developer Tools. If you fire them up and head over to the Application tab and with the Service Workers menu option selected, you should notice your service worker is installed for the current page.

This is as we expect, the service is installed and controlling the page. Each time we refresh the page, we will retrieve the dog image from cache instead of the network. In order to simulate an update to our service worker, I am going to make a slight change to the service-worker.js file.

const cacheName = ‘secondVersion';

In the code above, I have simply updated the name of the cache to secondVersion. This small change will let the browser know that we have a new service worker ready to rock and roll. If I refresh the page, I will now get prompted with the popup notifying me that there is a newer version available. Using Chrome Developer tools, I can see the new service worker is waiting to be activated; notice the Status section now has two versions, each with a different status.

Service Worker waiting - Chrome Developer Tools

If you click the refresh link on the notification bar on our web page, the new service worker will now be installed and controlling the page. You can verify this by opening up the developer tools and heading over to the Application tab. You should notice the new service worker is installed and controlling the current page. You can see this in the image below by referring to the version number under the Status section.

Service Worker Installed - Chrome Developer Tools

Conclusion

Using a technique like this notification is a great way to ensure that you keep your Progressive Web App lightning fast and retain all of the magic of service worker caching, while at the same time keeping the latest version of your service worker live!

If you’d like to see the full working code for this example, please head over to the repo at github.com/deanhume/pwa-update-available.

I have to admit, it took me a while to figure all of this out, and I couldn’t have done so without the following articles. I highly recommend reading them to learn more.