Your PWA Still Shows the Old Version After Deploy
You deploy a fix. You reload. It’s there. Someone messages you an hour later with a screenshot of the bug you just fixed.
You tell them to hard-refresh. Sometimes that works, which is the worst possible outcome, because now you believe hard-refreshing is the fix.
It isn’t. Your service worker is answering the request before the network ever hears about it.
What’s actually happening
A service worker installed by your PWA sits between the browser and the network. On a cache-first strategy — which is what most starter service workers use, and what makes a PWA work offline — the flow is:
- Page asks for
app.js - Service worker checks its cache, finds
app.js, returns it - The network is never contacted
Your new app.js is sitting on the server, perfectly deployed, and nobody is asking for it. Ctrl+Shift+R sometimes bypasses it and sometimes doesn’t, depending on the browser and what’s being requested. On an installed PWA with no visible address bar, the user has no way to hard-refresh at all.
The fix
The service worker only replaces its cache when the cache name changes. So the cache name has to change on every deploy:
// sw.js
const CACHE_NAME = 'app-v37'; // <-- this number must go up every single deploy
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))
)
);
});
Bump v37 to v38, deploy, and on next visit the worker installs a fresh cache and deletes the old one.
That’s the whole fix. Which is exactly why it keeps happening — it’s one character, it’s in a file you rarely open, and forgetting it produces no error. The deploy succeeds. The site is live. The old version is what people see.
Don’t rely on remembering
I forgot it enough times to stop trusting myself, so the version bump is no longer a thing I do. It’s a step in the deploy script:
# bump the service worker cache version, then deploy
n=$(grep -oE "app-v([0-9]+)" sw.js | head -1 | grep -oE "[0-9]+")
sed -i "s/app-v$n/app-v$((n+1))/" sw.js
If you’d rather not touch the file automatically, at least make the deploy refuse to run when the version is unchanged from the last deployed copy. A deploy that stops and says “you didn’t bump the cache” is worth more than a checklist item, because the checklist is only read by someone who already remembered.
How to confirm it actually worked
Not by reloading your own browser — yours is the one machine most likely to be clean.
Open DevTools → Application → Service Workers on a device that had the old version, and check that the active worker’s cache name matches what you just deployed. Or check from outside the browser entirely:
curl -s https://your-site/sw.js | grep -oE "app-v[0-9]+"
That tells you what the server is handing out. If it matches your new number and a user still sees the old page, they simply haven’t revisited yet — the worker updates on the next navigation, not instantly.
The bit that generalizes
A stale PWA is a specific instance of a general shape: the system is working exactly as designed, and the design’s success mode is indistinguishable from failure. Offline-capable caching and “serving a stale build” are the same behavior seen from two directions.
Whenever a feature’s correct behavior can look identical to a bug, you need a signal that isn’t the feature itself. Here it’s the version string, checked from outside the browser doing the caching.
Comments
Loading comments…