Skip to content

Update Flow

Deploying a new build means your service worker file changes — at minimum, version in $service-worker changes, causing `cache-\${version}` to be a new string. The browser detects this, downloads the new SW, and enters the update lifecycle described below.

Built-in SW (src/service-worker.js): SvelteKit registers it automatically during build. In development the type is 'module'; in production it is 'classic'. You never need to call navigator.serviceWorker.register() yourself.

@vite-pwa/sveltekit: The plugin handles registration via the registerType option. The virtual:pwa-register module (and its Svelte-specific variant virtual:pwa-register/svelte) exposes Svelte stores you can use in any component.

sequenceDiagram
  participant B as Browser
  participant OSW as Old SW (active)
  participant NSW as New SW (waiting)
  participant P as Page

  B->>NSW: Download + parse new sw.js
  NSW->>NSW: install event (precache new assets)
  NSW-->>OSW: waiting — old SW still controls tabs
  P->>P: updatefound event fires
  note over P: show reload banner
  P->>NSW: postMessage SKIP_WAITING
  NSW->>NSW: skipWaiting() — activates immediately
  NSW->>B: clients.claim() — takes over open tabs
  P->>P: reload
Service worker update sequence

Option A: autoUpdate with @vite-pwa/sveltekit

Section titled “Option A: autoUpdate with @vite-pwa/sveltekit”
vite.config.ts
SvelteKitPWA({
registerType: 'autoUpdate',
})

The new SW activates the moment its install phase completes — no user prompt. Best for apps where asset URLs are fingerprinted (hashed), so old and new assets cannot collide.

Option B: prompt with virtual:pwa-register/svelte

Section titled “Option B: prompt with virtual:pwa-register/svelte”
vite.config.ts
SvelteKitPWA({
registerType: 'prompt',
})

The new SW waits. Your Svelte component uses useRegisterSW from virtual:pwa-register/svelte to show a banner when an update is ready:

src/lib/ReloadPrompt.svelte
<script lang="ts">
import { useRegisterSW } from 'virtual:pwa-register/svelte';
const { needRefresh, offlineReady, updateServiceWorker } = useRegisterSW({
onRegistered(r) {
console.log('SW registered:', r);
},
onRegisterError(error) {
console.error('SW registration error', error);
},
});
function close() {
offlineReady.set(false);
needRefresh.set(false);
}
$: toast = $offlineReady || $needRefresh;
</script>
{#if toast}
<div class="pwa-toast" role="alert">
{#if $offlineReady}
<span>App ready to work offline.</span>
{:else}
<span>New version available.</span>
{/if}
{#if $needRefresh}
<button on:click={() => updateServiceWorker(true)}>Reload</button>
{/if}
<button on:click={close}>Close</button>
</div>
{/if}

updateServiceWorker(true) sends SKIP_WAITING to the waiting SW and reloads the page once it activates.

Option C: manual updatefound / statechange (built-in SW)

Section titled “Option C: manual updatefound / statechange (built-in SW)”

When you use src/service-worker.js without @vite-pwa/sveltekit, wire the update detection yourself:

// src/app.ts (or any module that runs on every page)
async function registerAndWatch() {
if (!('serviceWorker' in navigator)) return;
const registration = await navigator.serviceWorker.register(
'/service-worker.js',
{ type: import.meta.env.DEV ? 'module' : 'classic' }
);
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
if (!newWorker) return;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
showUpdateBanner(registration);
}
});
});
}
function showUpdateBanner(registration) {
const banner = document.createElement('div');
banner.textContent = 'New version available. ';
const btn = document.createElement('button');
btn.textContent = 'Reload';
btn.onclick = () => {
registration.waiting?.postMessage({ type: 'SKIP_WAITING' });
window.location.reload();
};
banner.appendChild(btn);
document.body.prepend(banner);
}
registerAndWatch();

Your service worker must listen for that message:

src/service-worker.js
self.addEventListener('message', (event) => {
if (event.data?.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});

SvelteKit apps are often SPAs — the user may stay on the same page for hours. Trigger a manual update check on an interval:

registration.addEventListener('updatefound', () => { /* … */ });
setInterval(() => registration.update(), 60 * 60 * 1000); // every hour
What does registerType: "autoUpdate" do when a new SW is installed?
Which virtual module exposes needRefresh and updateServiceWorker as Svelte stores?
In the manual update pattern, what message type does the page send to the waiting SW?
Why is it important to keep skipWaiting() user-driven rather than calling it unconditionally in install?