17

By default skipWaiting is set to false in Workbox. Assuming you're only using the Workbox-created service worker for caching, are there any downsides to setting this to true? Without doing so, the next build of your app will ship updated resource urls (from webpack). These urls will be updated in the service worker's precache manifest, but without skipWaiting, the updated service worker will not be activated to take advantage of them, until the user closes their browser and re-opens.

These updated resources will be loaded correctly (webpack will load the resources with the new hash values in them), but they'll never be cached until, again, the user closes all open browsers running the service worker, and re-opens. Under these circumstances, is there any reason to not just set skipWaiting to true?

As a related issue, what exactly does clientsClaim control? The above problem is fixed by merely switching skipWaiting on; so what does clientsClaim do?

1 Answer 1

30

As background, I'd recommend reading "The Service Worker Lifecycle". It's written from the generic service worker perspective, but the points apply to Workbox-powered service workers as well.

These concepts are also explained in more detail, from the perspective of using create-react-app, in this "Paying Attention while Loading Lazily" talk and microsite.

Workbox's implementation

Workbox uses an install event handler to cache new or updated entries in the precache manifest (appending a __WB_REVISION__ query param to the entries' URLs when needed, to avoid overwriting existing entries with different revisions), and it uses the activate event handler to delete previously cached entries that are no longer listed in the precache manifest.

If skipWaiting is true, then activate handler, which is responsible for cleaning up outdated URLs from the cache, would be executed immediately after an updated service worker is installed.

Under these circumstances, is there any reason to not just set skipWaiting to true?

There are a few risks to using skipWaiting, and to err on the side of safety, we disable it by default in Workbox. (It was enabled by default in older, sw-precache-generated service workers.)

Let's assume a scenario in which your HTML is loaded cache-first (generally a best practice), and then sometime after that load, a service worker update is detected.

Risk 1: Lazy-loading of fingerprinted content

Modern web apps often combine two techniques: asynchronous lazy-loading of resources only when needed (e.g. switching to a new view in a single page app) and adding fingerprints (hashes) to uniquely identify URLs based on the content they contain.

Traditionally, it's either the HTML itself (or some JavaScript that contains route information which is also loaded early on in the page lifecycle) that contains a reference to the current list of URLs that need to be lazy-loaded.

Let's say that the version of your web app which was initially loaded (before the service worker update happened) thinks that the URL /view-one.abcd1234.js needs to be loaded in order to render /view-one. But in the meantime, you've deployed an update to your code, and /view-one.abcd1234.js has been replaced on your server and in your precache manifest by /view-one.7890abcd.js.

If skipWaiting is true, then /view-one.abcd1234.js will be purged from the caches as part of the activate event. Chances are that you've already purged it from your server as part of your deployment as well. So that request will fail.

If skipWaiting is false, then /view-one.abcd1234.js will continue to be available in your caches until all the open clients of the service worker have been closed. That's usually what you'd want.

Note: While using a service worker can make it more likely to run into this class of problem, it's an issue for all web apps that lazy-load versioned URLs. You should always have error handling in place to detect lazy-loading failures and attempt to recover by, e.g., forcibly reloading your page. If you have that recovery logic in place, and you're okay with that UX, you might choose to enable skipWaiting anyway.

Risk 2: Lazy-loading of incompatible logic

This is similar to the first risk, but it applies when you don't have hash fingerprints in your URLs. If you deploy an update to your HTML and a corresponding update to one of your views, existing clients can end lazy-loading a version of /view-one.js that doesn't correspond to the structure of the HTML that was fetched from the cache.

If skipWaiting is false, then this isn't likely to happen, since the /view-one.js that's loaded from the cache should correspond to the HTML structure that was loaded from the same cache. That's why it's a safer default.

Note: Like before, this isn't a risk that's unique to service workers—any web app that dynamically loads unversioned URLs might end up loading incompatible logic following a recent deployment.

so what does clientsClaim do?

clientsClaim ensures that all uncontrolled clients (i.e. pages) that are within scope will be controlled by a service worker immediately after that service worker activates. If you don't enable it, then they won't be controlled until the next navigation.

It's usually safer to enable than skipWaiting, and can be helpful if you want the service worker to start populating runtime caches sooner rather than later.

I'll refer you to the section in the Service Worker Lifecycle doc for more info.

7
  • I don't think your first Risk 1 is100% correct. When the new service worker code and the change to view-one file gets uploaded to the server, install/activate event doesn't get called automatically.So if user clicks on view-one link,he will still see the old /view-one.abcd1234.js as it's not purged from the cache yet. Service worker gets updated if browser refreshes the page.Your risk comes into place when there're 2 tabs open and when updating the view-one and service worker code on the server, user refreshes one tab.now,if user clicks on that route in first tab, that's an error.makes sense? Commented Feb 4, 2020 at 13:36
  • 1
    Risk 1 can occur with a single tab when you're serving precached HTML and lazy-loading subresources, and you call skipWaiting(). The "old" precached HTML will be used to fulfill the navigation prior to the fresh SW being installed, and then once the fresh SW is installed, the use of skipWaiting() means that the "old" HTML will refer to hashed subresources that are no longer precached. I go into more detail about this at pawll.glitch.me Commented Feb 11, 2020 at 17:05
  • I think the point of confusion is when you say "SW gets updated if browser refreshes the page", but that's not true. The SW update check is asynchronous and starts during a navigation, but by the time the check completes and a new SW is detected, the navigation request might already be fulfilled using previously cached HTML. An update to the SW can occur immediately following any navigation. Commented Feb 11, 2020 at 17:08
  • So, @JeffPosnick Question 1) by navigation you mean you execute navigator.serviceWorker.register('/sw.js').then - every time user moves to my site's another route or link, right? Question 2) as you're a lot experienced in this, do you think skipWaiting is only risky when the application is SPA? and are there any more issues skipWaiting might create for me other than these 2 risks you explained in this answer? Question 3) so you suggest putting register call in some kind of listener when the route changes for SPA right? Commented Feb 11, 2020 at 21:32
  • Question 4) . redfin.engineering/… if you look at first approach he uses, i think he is wrong. v1 won't try to load styles from network, as it's already in the cache that v2 put. don't you agree? Commented Feb 12, 2020 at 9:42

Not the answer you're looking for? Browse other questions tagged or ask your own question.