47
Create a PWA with Sveltekit | Svelte
I've been using sveltekit and svelte for over a year now. I'm also waiting for it to be matured enough to beat NextJs in terms of community. But I like them both.
So, on this weekend I wanted to turn one of my SvelteKit projects into a PWA. When I wanted to do the same with NextJs projects there were a lot of tutorials. But I couldn't find many guides for svelte beginners.
That's because svelte has pwa functionality built into it.
- A website
- manifest.json [ basic icons,names,shortcuts]
- service-worker [ for offline cache ]
So let's get on with it.

We'll create a demo Sveltekit project:
npm init svelte@next my-app

Then we'll choose a simple config in vite for the sake of this article:

Choose typescript because #typescriptgang
:

Now we have a demo project set up with typescript, it will be straight-forward from here on:

Let's get into our directory:
cd my-app
And run:
yarn

After that,
- In the /static directory, We'll create a manifest.json.
- When svelte compiles the whole application, it copies static files over to the build folder.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"short_name": "svelte-pwa", | |
"name": "svelte-test-pwa", | |
"start_url": "/", | |
"icons": [ | |
{ | |
"src": "logo_512.png", | |
"type": "image/png", | |
"sizes": "512x512" | |
} | |
], | |
"background_color": "#3367D6", | |
"display": "standalone", | |
"scope": "/", | |
"theme_color": "#3367D6", | |
"shortcuts": [ | |
{ | |
"name": "How's weather today?", | |
"short_name": "Today", | |
"description": "View weather information for today", | |
"url": "/today?source=pwa", | |
"icons": [{ "src": "/icons/logo_192.png", "sizes": "192x192" }] | |
}, | |
{ | |
"name": "How's weather tomorrow?", | |
"short_name": "Tomorrow", | |
"description": "View weather information for tomorrow", | |
"url": "/tomorrow?source=pwa", | |
"icons": [{ "src": "/icons/logo_192.png", "sizes": "192x192" }] | |
} | |
], | |
"description": "Lofi to lofi beats, while using the scratchpad to list down tasks and thoughts" | |
} |
Then we'll refer our manifest.json
in src/app.html
.

And finally we'll create our src/service-worker.ts
Svelte will automatically detect the service-worker in the src folder's root and then register our service worker during build.
Isn't that neat?

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <reference lib="webworker" /> | |
import { build, files, timestamp } from '$service-worker'; | |
const worker = (self as unknown) as ServiceWorkerGlobalScope; | |
const FILES = `cache${timestamp}`; | |
// `build` is an array of all the files generated by the bundler, | |
// `files` is an array of everything in the `static` directory | |
const to_cache = build.concat(files); | |
const staticAssets = new Set(to_cache); | |
worker.addEventListener('install', (event) => { | |
event.waitUntil( | |
caches | |
.open(FILES) | |
.then((cache) => cache.addAll(to_cache)) | |
.then(() => { | |
worker.skipWaiting(); | |
}) | |
); | |
}); | |
worker.addEventListener('activate', (event) => { | |
event.waitUntil( | |
caches.keys().then(async (keys) => { | |
// delete old caches | |
for (const key of keys) { | |
if (key !== FILES) await caches.delete(key); | |
} | |
worker.clients.claim(); | |
}) | |
); | |
}); | |
/** | |
* Fetch the asset from the network and store it in the cache. | |
* Fall back to the cache if the user is offline. | |
*/ | |
async function fetchAndCache(request: Request) { | |
const cache = await caches.open(`offline${timestamp}`); | |
try { | |
const response = await fetch(request); | |
cache.put(request, response.clone()); | |
return response; | |
} catch (err) { | |
const response = await cache.match(request); | |
if (response) return response; | |
throw err; | |
} | |
} | |
worker.addEventListener('fetch', (event) => { | |
if (event.request.method !== 'GET' || event.request.headers.has('range')) return; | |
const url = new URL(event.request.url); | |
// don't try to handle e.g. data: URIs | |
const isHttp = url.protocol.startsWith('http'); | |
const isDevServerRequest = | |
url.hostname === self.location.hostname && url.port !== self.location.port; | |
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname); | |
const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset; | |
if (isHttp && !isDevServerRequest && !skipBecauseUncached) { | |
event.respondWith( | |
(async () => { | |
// always serve static files and bundler-generated assets from cache. | |
// if your application has other URLs with data that will never change, | |
// set this variable to true for them and they will only be fetched once. | |
const cachedAsset = isStaticAsset && (await caches.match(event.request)); | |
return cachedAsset || fetchAndCache(event.request); | |
})() | |
); | |
} | |
}); |
Now we can preview our build using yarn preview
:

😯 thats the 'install app' button,

Svelte makes it easy to make PWAs.
The source code of this project lies at:
You can find me at:
47