When a service worker updates, it doesn't take control of the page right way; it goes into a "waiting" state, waiting to be activated.
Surprisingly, the updated service worker doesn't even take control of the tab after refreshing the page. Google explains:
https://developers.google.com/web/fundamentals/instant-and-offline/service-worker/lifecycle
Even if you only have one tab open to the demo, refreshing the page isn't enough to let the new version take over. This is due to how browser navigations work. When you navigate, the current page doesn't go away until the response headers have been received, and even then the current page may stay if the response has a
Content-Disposition
header. Because of this overlap, the current service worker is always controlling a client during a refresh.To get the update, close or navigate away from all tabs using the current service worker. Then, when you navigate to the demo again, you should see the horse [updated content].
This pattern is similar to how Chrome updates. Updates to Chrome download in the background, but don't apply until Chrome restarts. In the mean time, you can continue to use the current version without disruption. However, this is a pain during development, but DevTools has ways to make it easier, which I'll cover later in this article.
This behavior makes sense in the case of multiple tabs. My app is a bundle that needs to be updated atomically; we can't mix and match part of the old bundle and part of the new bundle. (In a native app, atomicity is automatic and guaranteed.)
But in the case of a single tab, which I'll call the "last open tab" of the app, this behavior is not what I want. I want refreshing the last open tab to update my service worker.
(It's hard to imagine that anybody actually wants the old service worker to continue running when the last open tab is refreshed. Google's "navigation overlap" argument sounds to me like a good excuse for an unfortunate bug.)
My app is normally only used in a single tab. In production, I want my users to be able to use the latest code just by refreshing the page, but that won't work: the old service worker will remain in control across refreshes.
I don't want to have to tell users, "to receive updates, be sure to close or navigate away from my app." I want to tell them, "just refresh."
How can I activate my updated service worker when the user refreshes the page?
EDIT: There's one answer I'm aware of that's quick, easy, and wrong: skipWaiting
in the service worker's install event. skipWaiting
will make the new service worker take effect as soon as the update downloads, while the old page tab is open. That makes the update unsafely nonatomic; it's like replacing a native app bundle while the app's running. That's not OK for me. I need to wait until the user refreshes the page of the last open tab.
skipWaiting
in the service worker'sinstall
event. I don't want to just randomly replace my service worker as soon as the update has been discovered, in the middle of users' work. I need to wait until the user refreshes the page. – Dan Fabulich