Motivation
A Progressive Web App should notify it’s operator when a new version is availble. It should make downloading the new version simple and effective.
Note: If you are using Update on reload, turn it off before proceeding.August 23, 2025
A Progressive Web App should notify it’s operator when a new version is availble. It should make downloading the new version simple and effective.
Note: If you are using Update on reload, turn it off before proceeding.First, add a listener to your service worker registration in your frontend code to detect when an update is available. The callback to this listenr will control showing a banner with a button that, once clicked, will download a new version of the service worker.
function onServiceWorkerUpdateFound(){// show a banner component indicating that an update is available}
const registration =
await navigator.serviceWorker.register(
"/serviceworker.js"
);
setRegistration(registration);
registration.addEventListener(
"updatefound",
onServiceWorkerUpdatedFound
);
On robertcunningham.app, I show a banner in the nav on deskotp, and just above the nav bar on mobile.
Next, set up your service worker to listen for messages from the client. This way, when the app operator clicks the Download button, we can trigger a message to the new service worker to skip its waiting phase and activate. Otherwise, all tabs on which the app is loaded must first be closed for the updated service worker to activate. Note that a service worker that skips waiting will lead to inconsistent service worker versions (in the case that the app operator has the app running in multiple tabs).
self.addEventListener("message", (event) => {
async function handleMessage(event) {}
event.waitUntil(handleMessage(event));
});
Your system and use-case of course will dictate the implementation details of handleMessage
, but on robertcunningham.app I call skipWaiting
and then send an acknowledgement message to the client.
async function handleMessage() {
console.log("[Service Worker] Message event", event);
const { data, source } = event;
const { action } = data;
const client = await self.clients.get(source.id);
if (!client) {
return;
}
if (action === "skip-waiting") {
await self.skipWaiting();
client.postMessage({ action });
}
}
Now that our service worker and client are listening for messages, everything is ready for the update to happen when the app operator clicks the button. Again, the implementation is up to you, but on robertcunningham.app I simply send a message to the service worker that the skip-waiting
action was requested. Note that the message must be sent to the waiting service worker, not the one which is currently active.
function onDownloadButtonClick() {
if (!registration) return;
if (!registration.waiting) {
window.location.reload();
return;
}
registration.waiting.postMessage({
action: "skip-waiting",
});
}
If there is no waiting registration, it means the app is install the service worker for the first time, so we can go ahead and just reload the page. Otherwise, there is one last step.
This last step is an easy step, and in fact was already mentioned; once the service worker receives the message to skip waiting and sends the acknowledgement back to the client, the client will then reload and the newly activated service worker will have control of fetch and push events.