Service workers
Edit this page on GitHubService workers act as proxy servers that handle network requests inside your app. This makes it possible to make your app work offline, but even if you don't need offline support (or can't realistically implement it because of the type of app you're building), it's often worth using service workers to speed up navigation by precaching your built JS and CSS.
In SvelteKit, if you have a src/service-worker.js
file (or src/service-worker.ts
, src/service-worker/index.js
, etc) it will be bundled and automatically registered. You can change the location of your service worker if you need to.
You can disable automatic registration if you need to register the service worker with your own logic or use another solution. The default registration looks something like this:
ts
if ('serviceWorker' innavigator ) {addEventListener ('load', function () {navigator .serviceWorker .register ('./path/to/service-worker.js');});}
Inside the service workerpermalink
Inside the service worker you have access to the $service-worker
module, which provides you with the paths to all static assets, build files and prerendered pages. You're also provided with an app version string which you can use for creating a unique cache name. If your Vite config specifies define
(used for global variable replacements), this will be applied to service workers as well as your server/client builds.
The following example caches the built app and any files in static
eagerly, and caches all other requests as they happen. This would make each page work offline once visited.
ts
import {build ,files ,version } from '$service-worker';// Create a unique cache name for this deploymentconstCACHE = `cache-${version }`;constASSETS = [...build , // the app itself...files // everything in `static`];self .addEventListener ('install', (event ) => {// Create a new cache and add all files to itasync functionaddFilesToCache () {constProperty 'waitUntil' does not exist on type 'Event'.2339Property 'waitUntil' does not exist on type 'Event'.cache = awaitcaches .open (CACHE );awaitcache .addAll (ASSETS );}event .waitUntil (addFilesToCache ());});self .addEventListener ('activate', (event ) => {// Remove previous cached data from diskasync functiondeleteOldCaches () {for (constkey of awaitcaches .keys ()) {if (Property 'waitUntil' does not exist on type 'Event'.2339Property 'waitUntil' does not exist on type 'Event'.key !==CACHE ) awaitcaches .delete (key );}}event .waitUntil (deleteOldCaches ());});Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.self .addEventListener ('fetch', (event ) => {// ignore POST requests etcProperty 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.if (event .request .method !== 'GET') return;async functionrespond () {consturl = newURL (event .request .url );constProperty 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.cache = awaitcaches .open (CACHE );// `build`/`files` can always be served from the cacheif (ASSETS .includes (url .pathname )) {returncache .match (event .request );}Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.// for everything else, try the network first, but// fall back to the cache if we're offlinetry {Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.constresponse = awaitfetch (event .request );if (response .status === 200) {cache .put (event .request ,response .clone ());}Property 'request' does not exist on type 'Event'.2339Property 'request' does not exist on type 'Event'.returnresponse ;} catch {returnProperty 'respondWith' does not exist on type 'Event'.2339Property 'respondWith' does not exist on type 'Event'.cache .match (event .request );}}event .respondWith (respond ());});
Be careful when caching! In some cases, stale data might be worse than data that's unavailable while offline. Since browsers will empty caches if they get too full, you should also be careful about caching large assets like video files.
During developmentpermalink
The service worker is bundled for production, but not during development. For that reason, only browsers that support modules in service workers will be able to use them at dev time. If you are manually registering your service worker, you will need to pass the { type: 'module' }
option in development:
ts
import {dev } from '$app/environment';navigator .serviceWorker .register ('/service-worker.js', {type :dev ? 'module' : 'classic'});
build
andprerendered
are empty arrays during development
Type safetypermalink
Setting up proper types for service workers requires some manual setup. Inside your service-worker.js
, add the following to the top of your file:
ts
/// <reference no-default-lib="true"/>/// <reference lib="esnext" />/// <reference lib="webworker" />constsw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {unknown} */ (self ));
ts
/// <reference no-default-lib="true"/>/// <reference lib="esnext" />/// <reference lib="webworker" />constsw =self as unknown asServiceWorkerGlobalScope ;
This disables access to DOM typings like HTMLElement
which are not available inside a service worker and instantiates the correct globals. The reassignment of self
to sw
allows you to type cast it in the process (there are a couple of ways to do this, but the easiest that requires no additional files). Use sw
instead of self
in the rest of the file.
Other solutionspermalink
SvelteKit's service worker implementation is deliberately low-level. If you need a more full-flegded but also more opinionated solution, we recommend looking at solutions like Vite PWA plugin, which uses Workbox. For more general information on service workers, we recommend the MDN web docs.