Skip to main content

If you have a PWA and you’ve recently looked at your console or app manifest, you may have noticed this message:

Page does not work offline. Starting in Chrome 93, the installability criteria is changing, and this site will not be installable. https://developer.chrome.com/blog/improved-pwa-offline-detection for more information

Or if you’re using Edge:

Site cannot be installed: Page does not work offline. The page will not be regarded as installable after Edge 93, stable release August 2021.

You see, before Chrome 93, all you had to do for Chrome to make your app installable was to pretend to listen to the fetch event in your service worker. You didn’t even really have to do anything at all. All you had to do was register a listener. And that was it. Easy-peasy.

I guess Google took issue with the hack. Truth be told, there are probably too many sites — many of which probably shouldn’t even be installable in the first place— that used ugly hacks like this:

self.addEventListener('fetch', function (event) {

});

With the upcoming release of Chrome, you’ll have to actually make use of the cache or your PWA will no longer be installable.

Why is this happening?

Google’s dogma is that a PWA should never display the Chrome’s default offline screen. Instead, all PWAs should either be available offline or show some sort of offline page as a fallback. Thanks to the hack above, you weren’t actually forced to make your PWA work offline. Now you will be (or you’ll have to find an even uglier hack to not have to).

Now, why a custom page that pretty much does nothing useful except say “you’re offline” is a better fallback than the default dinosaur page is a bit unclear. People do know what that page means. It offers clears directions on what to do to get back online. And it’s built by Google itself (so it’s not like it’s redirecting traffic to some unrelated third-party site).

But apparently, Google doesn’t like its own solution. So instead you’ll have to build one yourself (which — in all likelihood — won’t be as good as Google’s. That is, unless you can do better than the Dinosaur Game obviously).

What can I do to fix this?

First, you’ll have to populate your “fetch” handler so that it actually does something useful, i.e. cache resources. There are two main ways to go about this.

  • With the Network-Falling-Back-To-Cache strategy, your service worker will first try to retrieve the resource from your server. Then when it can’t do that — because for example, you’re offline — retrieve it from the cache (if it exists there).
self.addEventListener('fetch', function(event) {
    event.respondWith(async function() {
       try{
         var res = await fetch(event.request);
         var cache = await caches.open('cache');
         cache.put(event.request.url, res.clone());
         return res;
       }
       catch(error){
         return caches.match(event.request);
        }
      }());
  });

With the Stale-While-Revalidate strategy, your service worker first looks into the cache while also issuing the request to the server. If the resource exists in the cache, it will send it back to the client right away — resulting in a seemingly instantaneous load. When (and if) the server responds to the request successfully, it will save the updated response in the cache. The main drawback of this approach is that resources that you’ll serve will always be one version behind.

self.addEventListener('fetch', function(event){
    event.respondWith(async function () {
       var cache = await caches.open('cache');
       var cachedResponsePromise = await cache.match(event.request);
       var networkResponsePromise = fetch(event.request);
       event.waitUntil(async function () {
          var networkResponse = await networkResponsePromise;
          await cache.put(event.request, networkResponse.clone());
       }());
       return cachedResponsePromise || networkResponsePromise;
     }());
 });

There are a couple of other marginally-useful strategies. With Cache-First, you don’t revalidate resources at all. Resources can never be updated once they’re in the cache — but the strategy can also lighten your AWS bill. With Network-Only, you don’t make use of the cache at all, so that resources are always fresh. And there is also a Cache-Only strategy, which is pretty much useless.

Secondly, you’ll also likely have to precache resources in the “install” event handler. Fun fact: this event fires when the service worker is installed. Not when the user has installed your app. For some reason, I see many developers getting these two concepts mixed up.

self.addEventListener('install', function(event) {
   event.waitUntil(
     caches.open('cache').then(function(cache) {
       return cache.addAll([
         "./app/",
         "./app/index.html",
         "./app/style.css",
         "./app/app.js"
        ]);
     })
    );
 });

The issue with resource caching

So there is one main issue that remains with the way service workers handle offline. As you update your app and create new resources, you’ll have to remember to precache them too if you want everything to always work offline. That’s potentially a lot of headaches — although a library like Workbox does make things a bit easier. Regardless, this highlights one of the worst aspects of PWAs. Making a PWA is not a one-time effort. There are a bunch of things you’ll have to update regularly or they’ll break.

Sebastián Becerra

Sebastián Becerra

Trabajo con tecnologías web en arquitecturas Cloud. Me gusta descubrir y aprender nuevas tecnologías en Layouts (CSS, Sass, Less), Frameworks de Desarrollo (JQuery, AngularJS, Ionic) y plataformas Web (Wordpress, Joomla, Prestashop). He colaborado con diferentes proyectos de software libre y código abierto con fines sociales. Además participé en distintos eventos TI exponiendo sobre comunidades y educando sobre tecnologías Web.

Leave a Reply