Skip to content

The $service-worker Module

SvelteKit automatically bundles and registers any file you place at src/service-worker.js (or .ts). Inside that file, the $service-worker virtual module gives you everything you need to write a robust precaching service worker without any third-party library.

ExportTypeContents
buildstring[]Hashed client-side assets (_app/immutable/…)
filesstring[]Everything copied from static/
versionstringUnique string that changes on every build
prerenderedstring[]Paths of prerendered HTML pages

build and files together cover the full set of assets your app needs to work offline. version makes a unique cache name so old caches are cleaned up reliably after each deploy.

/// <reference types="@sveltejs/kit" />
import { build, files, version, prerendered } from '$service-worker';
const CACHE = `cache-\${version}`;
const ASSETS = [
...build, // hashed JS/CSS bundles
...files, // static/ assets (icons, fonts, …)
...prerendered, // prerendered HTML pages
];
// -- Install: precache all assets ----------------------------------------------
self.addEventListener('install', (event) => {
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
event.waitUntil(addFilesToCache());
});
// -- Activate: delete old caches -----------------------------------------------
self.addEventListener('activate', (event) => {
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
// -- Fetch: serve from cache, fall back to network -----------------------------
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// Always serve build/files assets from cache (they are hashed)
if (ASSETS.includes(url.pathname)) {
const cached = await cache.match(url.pathname);
if (cached) return cached;
}
// For everything else: network first, cache as fallback
try {
const response = await fetch(event.request);
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch {
const cached = await cache.match(event.request);
if (cached) return cached;
throw new Error('No cached response and network unavailable');
}
}
event.respondWith(respond());
});

Every SvelteKit build produces a fresh version string derived from the build timestamp and content hash. The pattern `cache-\${version}` means each deployment gets its own isolated cache. The activate handler then deletes every cache whose name is not the current version — automatically cleaning up storage from all previous deploys.

For TypeScript projects, rename the file to src/service-worker.ts and add the reference directive so the SW globals are typed correctly:

/// <reference types="@sveltejs/kit" />
import { build, files, version, prerendered } from '$service-worker';
declare const self: ServiceWorkerGlobalScope;
const CACHE = `cache-\${version}`;
// … rest of the file is identical

If your app prerender pages (via export const prerender = true in route files or adapter-static), SvelteKit populates prerendered with their paths. Including them in ASSETS means those pages load instantly offline, without a network round-trip.

What does the build export from $service-worker contain?
Why is the cache named using the version export?
Which event is the right place to delete stale caches from previous deployments?
Which $service-worker export lists paths to prerendered HTML pages?