Problem
The web UI does not update in browsers that have a long-lived open tab, even after dist/ is rebuilt and re-served. This is a service-worker caching issue, not a backend issue.
VitePWA (vite.config.ts) ships a service worker that precaches the app shell:
workbox: {
navigateFallback: "/index.html",
globPatterns: ["**/*.{js,css,html,svg,png,woff2}"], // precaches JS/CSS bundles
...
}
The SW intercepts requests and serves cached JS/CSS before hitting the server, so rebuilds don't reach those clients.
Why only some browsers
The generated registerSW.js is the minimal stub (from registerType: "autoUpdate"):
if('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js', { scope: '/' })
})
}
It relies on skipWaiting + clientsClaim but has no reload-on-update logic, and the app never imports virtual:pwa-register. As a result:
- The browser only checks for a new
sw.js on a full navigation/reload (or ~every 24h). pi-web is a long-lived SPA tab, so tabs that are never reloaded never pick up a new SW.
- Even when a new SW activates, the already-loaded page keeps running the old JS until reloaded — there is no
controllerchange listener to force a refresh.
Recommended fix
Add auto-reload-on-update so clients get new code without manual intervention. Either:
- Add a one-time
controllerchange listener that reloads:
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) return;
refreshing = true;
window.location.reload();
});
- Or wire up
virtual:pwa-register with registerSW({ immediate: true }) so vite-plugin-pwa's full auto-update flow runs instead of the minimal stub.
Workaround (already applied)
Per stuck browser: DevTools → Application → Service Workers → Unregister (or enable "Update on reload"), then hard reload. Or close all tabs of the app and reopen.
Problem
The web UI does not update in browsers that have a long-lived open tab, even after
dist/is rebuilt and re-served. This is a service-worker caching issue, not a backend issue.VitePWA(vite.config.ts) ships a service worker that precaches the app shell:The SW intercepts requests and serves cached JS/CSS before hitting the server, so rebuilds don't reach those clients.
Why only some browsers
The generated
registerSW.jsis the minimal stub (fromregisterType: "autoUpdate"):It relies on
skipWaiting+clientsClaimbut has no reload-on-update logic, and the app never importsvirtual:pwa-register. As a result:sw.json a full navigation/reload (or ~every 24h). pi-web is a long-lived SPA tab, so tabs that are never reloaded never pick up a new SW.controllerchangelistener to force a refresh.Recommended fix
Add auto-reload-on-update so clients get new code without manual intervention. Either:
controllerchangelistener that reloads:virtual:pwa-registerwithregisterSW({ immediate: true })so vite-plugin-pwa's full auto-update flow runs instead of the minimal stub.Workaround (already applied)
Per stuck browser: DevTools → Application → Service Workers → Unregister (or enable "Update on reload"), then hard reload. Or close all tabs of the app and reopen.