Situations in which service worker may display old version of app even after new version is loaded?

service worker update cache
pwa update cache
pwa caching strategies
service worker cache example
service worker application cache
clear service worker cache programmatically
service worker cache api calls
workbox

Sorry, I don't have a reproducible test case of this issue because I've never even seen it happen myself. I only know it happens because of client-side logging in my app and complaints from users.

The problem is:

  1. I deploy a new version of my app
  2. User visits my site, gets the new version, and runs it successfully
  3. User visits my site again and gets an old version of my app

I'm using a service worker, which I was hoping could provide some guarantees about that scenario not happening. It's particularly troubling when the new version includes an IndexedDB schema migration, because then old version of my app won't even work anymore.

More details:

I'm using Workbox 4.3.1. My service worker is basically:

importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
workbox.precaching.precacheAndRoute([]);
workbox.routing.registerNavigationRoute("/index.html", {
    blacklist: [
        new RegExp("^/static"),
        new RegExp("^/sw.js"),
    ],
});

workbox.precaching.precacheAndRoute([]); gets filled in by workboxBuild.injectManifest. I can manually confirm that the right files get filled in. And generally the service worker works. I can see it in the browser dev tools. I can disconnect from the Internet and still use my app. Everything seems fine. Like I said above, I've never seen this problem happen, and I don't have a reproducible test case.

But some of my users experienced the problem described above. I tried to use client-side error logging to investigate. I added some code to my app to store its version number in localStorage, and on initial load it compares that version number against the current running version number. If the version in localStorage is more recent than the current running version (i.e., it successfully ran a newer version in the past but now is going back to an older version), it logs the version numbers along with some additional information:

let registrations = [];
if (window.navigator.serviceWorker) {
    registrations = await window.navigator.serviceWorker.getRegistrations();
}
log({
    hasNavigatorServiceWorker:
        window.navigator.serviceWorker !== undefined,
    registrationsLength: registrations.length,
    registrations: registrations.map(r => {
        return {
            scope: r.scope,
            active: r.active
                ? {
                      scriptURL: r.active.scriptURL,
                      state: r.active.state,
                  }
                : null,
            installing: r.installing
                ? {
                      scriptURL: r.installing.scriptURL,
                      state: r.installing.state,
                  }
                : null,
            waiting: r.waiting
                ? {
                      scriptURL: r.waiting.scriptURL,
                      state: r.waiting.state,
                  }
                : null,
        };
    }),
})

Looking in my logs, I see that this problem occurs for only like 1% of my users. Firefox is enriched among these users (4% of overall traffic, but 18% of log entries for this problem), but it happens for all browsers and operating systems.

And I see that almost all records have these values:

{
    hasNavigatorServiceWorker: true,
    registrationsLength: 1,
    registrations: [{
        "scope": "https://example.com/",
        "active": {
            "scriptURL": "https://example.com/sw.js",
            "state": "activated"
        },
        "installing": null,
        "waiting": null
    }]
}

As far as I know, those are all correct values.

I should also note that my JavaScript files have a hash in the URL, so it cannot be that my server is somehow returning an old version of my JavaScript when the user requests a new version.

So, what could be going on? How can this observed behavior be explained? What else could I be logging to debug this further?

The only scenarios I can come up with seem highly implausible to me. Like...

  1. User loads v1, service worker fails for some reason
  2. User loads v2, service worker fails for some reason
  3. User somehow gets v1 from their browser cache, since all prior service workers failed, but now the service worker works correctly and stores this as the current version

But I have no evidence of the service worker ever failing. Nothing in my error logs. No user complaining that offline support is broken.

If it helps, the actual website where this happens is https://play.basketball-gm.com/, the service worker is at https://play.basketball-gm.com/sw.js, and all the code is available on GitHub.

And this problem has been going on ever since I started using a service worker, about a year ago. I am just finally getting around to writing up a Stack Overflow question, because I've given up hope that I'll be able to figure it out on my own or even create a reproducible test case.

Somehow user gets the old version from their cache. Removing outdated caches should do the trick. Once a new service worker has installed and a previous version isn't being used, the new one activates, and you get an activate event. Because the old version is out of the way, it's a good time to delete unused caches.

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // Return true if you want to remove this cache,
          // but remember that caches are shared across
          // the whole origin
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
});

During activation, other events such as fetch are put into a queue, so a long activation could potentially block page loads. Keep your activation as lean as possible, only using it for things you couldn't do while the old version was active.

Help debugging situations in which service worker may display an , Help debugging situations in which service worker may display an old version of my app even after new version is loaded? #2269. Open. Situations in which service worker may display old version of app even after new version is loaded? 1 Service worker with range requests plugin: cannot fetch mp3 files when offline

This may be more of a 'tech support' answer for troubled users, in my frame of thought.

I have run into similar issues personally when trying to re-load my own React applications locally, during development. (Client-Side code) I was using a tunneling service, ngrok.

My site would continue to function, even after I killed the service with an older version of the code. (I found this frustrating, after implementing a fix and it appearing to not work)

(Tech Support) Answer:

It may be necessary to perform a 'Hard Reload', sometimes a 'Empty Cache and Hard Reload', this should cleanse the browser. (It really depends on the structure of the files loaded with the site).

You can access these options while the developer console is open.

For more details on hard refresh, you can reference this SO post: whats-the-difference-between-normal-reload-hard-reload-and-empty-cache-a

4. Service Worker Lifecycle and Cache Management, You may have even encountered situations where you changed the service worker And yet, just like a cheat-code in an old-school video game, this convenient for app.js and serves app-sw.js instead—a version that knows how to display a But why are new pages that loaded after the service worker finished installing  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.

A year later and I've finally figured it out.

I was precaching an asset that was blocked by some ad blockers. This caused service worker installation to fail, keeping the old service worker around longer than intended. Somehow this failure was not caught by my client side error logging.

Workbox Window, To simplify the process of service worker registration and updates by helping developers works with npm dependencies, it's possible to use them to load workbox-window . `event.isUpdate` will be true if another version of the service // worker was console.log(`A new service worker has installed, but it can't activate` + If I do find it, I compare versions and if there is a newer one on the server, I throw up a dialog. Dismissing this dialog (my ok button is labeled “update”) runs window.location.reload(true) and then stores the new versionstring in localstorage; RESULT: My app is updated!!

Offline caching with Service Worker Precache, You're viewing an older version of Polymer. Please see On subsequent visits, the service worker can load resources directly from the cache. If the user is  It is only activated when there are no longer any pages loaded that are still using the old service worker. As soon as there are no more such pages still loaded, the new service worker activates. You’ll want to update your install event listener in the new service worker to something like this (notice the new version number):

Popular Mechanics, Now as we embark on a new century, we present this special issue, our I 177th Join us for a look at this century and also a look ahead at what may be in store  Even though you can cache tons of files, the Service Worker only checks the hash of your registered service-worker.js. If that file has only 1 little change in it, it will be treated as a new version.

Service Worker API, Service workers essentially act as proxy servers that sit between web over how your app behaves in certain situations (the most obvious one being when As soon as there are no more pages to be loaded, the new service worker other things associated with the previous version of your service worker. The point where this event fires is generally a good time to clean up old caches and other things associated with the previous version of your service worker. Your service worker can respond to requests using the FetchEvent event. You can modify the response to these requests in any way you want, using the FetchEvent.respondWith method.

Comments
  • But why would they get the old version from the cache, when they have already loaded a newer version?
  • If the amount of cached data exceeds the browser's storage limit, the browser will begin evicting all data associated with an origin, one origin at a time, until the storage amount goes under the limit again.
  • That's true.. but shouldn't it start with the oldest data, not the newest? And doesn't Workbox automatically delete its old data anyway?
  • It is already starting from oldest data to the newest. And no it doesn't automatically delete. You can use this code to delete manually.
  • But if it goes from oldest to newest, then the behavior I observed in my question wouldn't happen. And Workbox does claim to delete old cached data automatically developers.google.com/web/tools/workbox/modules/… which seems to work when I test it - I'm not talking about the raw Service Worker API.