Building a PWA with Svelte
Tags: svelte, webdev, tutorial
Published: 2021-10-04 18:04:14

**Written by [Andrew Evans](https://blog.logrocket.com/author/andrewevans/)** ✏️

Similar to native applications, progressive web apps (PWAs) are a popular solution to running web applications on mobile devices. With PWAs, users can experience web applications with the same ease and familiarity as mobile apps; similarly, companies are able to host apps directly on the internet instead of different mobile app stores.

In this tutorial, we’ll create our own PWA with Svelte, a modern framework that is based on imperative vs. declarative applications. With traditional frameworks like React, you must individually build out all of the pieces of your application; however, Svelte can pre-render your application’s build with just a small amount of code.

First, we’ll explore how PWAs work by building our own in Svelte. Then, we’ll look at some features of Sapper and SvelteKit that you can use to bring PWA features to your apps. We’ll be referencing a sample project that can be accessed at my repo on [GitHub](https://github.com/andrewevans0102/building-a-pwa-with-svlete). Let’s get started!

## PWA features

Before building our own, let’s take a closer look at how PWAs work. The following two features [work in conjunction with progressive enhancement](https://blog.logrocket.com/a-quick-introduction-to-progressive-enhancement/) to create an experience similar to native applications.

Service workers act as intermediaries or proxies for web applications, enabling the use of caching resources and handling poor internet connections gracefully.

For example, when internet connection is lost, we may use a service worker to display a message to a user so that the app does not crash suddenly. Similarly, a service worker may cause our app to save local activity and resync after regaining internet connection.

Web manifests enable users to download or install apps on specific platforms. The app that the user sees in the browser can be run offline or in a state similar to a native implementation. Although manifests are still considered experimental, they are heavily supported by modern browsers.

## Build a PWA with Svelte

Now that we understand the features that define PWAs, let’s build our own using Svelte. The example found in this section is based on an example from [GitHub](https://github.com/tretapey/svelte-pwa).

First, head to your terminal and create a new Svelte project by running the code below:

```bash
npx degit sveltejs/template svelte-pwa
```

Next, we’ll install our JavaScript dependencies. Navigate into the directory that you just created and run the code below:

```bash
cd svelte-pwa
npm install
```

When we used `degit` to create a new project, Svelte scaffolded a basic app with the following structure:

![Scaffolded Svelte App Structure](https://blog.logrocket.com/wp-content/uploads/2021/09/scaffolded-svelte-app-structure.png)

We’ll primarily be working with the files in the `src` directory. The `App.svelte` file is our project’s entry point and contains the following code:

```html
<script>
    export let name;
</script>
<main>
    <h1>Hello {name}!</h1>
    <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }
    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }
    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }
</style>
```

`.svelte` files have separate sections for styles, the view `html`, and any JavaScript code `<scripts>`.

Add the following code to your `main.js` file to bootstrap your app and tell the bundler to build the project with your `.svelte` App file:

```javascript
import App from './App.svelte';
const app = new App({
    target: document.body,
    props: {
        name: 'world'
    }
});
export default app;
```

To create a PWA, first, we’ll create a service worker inside of the `service-worker.js` file in the `public` directory:

```javascript
"use strict";
// Cache Name
const CACHE_NAME = "static-cache-v1";
// Cache Files
const FILES_TO_CACHE = ["/offline.html"];
// install
self.addEventListener("install", (evt) => {
  console.log("[ServiceWorker] Install");
  evt.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      console.log("[ServiceWorker] Pre-caching offline page");
      return cache.addAll(FILES_TO_CACHE);
    })
  );
  self.skipWaiting();
});
// Active PWA Cache and clear out anything older
self.addEventListener("activate", (evt) => {
  console.log("[ServiceWorker] Activate");
  evt.waitUntil(
    caches.keys().then((keyList) => {
      return Promise.all(
        keyList.map((key) => {
          if (key !== CACHE_NAME) {
            console.log("[ServiceWorker] Removing old cache", key);
            return caches.delete(key);
          }
        })
      );
    })
  );
  self.clients.claim();
});
// listen for fetch events in page navigation and return anything that has been cached
self.addEventListener("fetch", (evt) => {
  console.log("[ServiceWorker] Fetch", evt.request.url);
  // when not a navigation event return
  if (evt.request.mode !== "navigate") {
    return;
  }
  evt.respondWith(
    fetch(evt.request).catch(() => {
      return caches.open(CACHE_NAME).then((cache) => {
        return cache.match("offline.html");
      });
    })
  );
});
```

In the code block above, we register events handled by the service worker, including `install` and `fetch` events. To handle offline usage of the project, we’ll cache files used in navigation and record what we cache. Using PWAs, you can cache only what is needed, improving your UX.

Next, we’ll create an `offline.html` file, which will be cached to navigation. Add the following code in the `public` directory:

![Svelte Cache Public Directory](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-cache-public-directory.png)

Now, let’s create a `manifest.json` file in the `public` directory:

![Svelte Manifest JSON](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-manifest-json.png)

You can use the `icons` entry to set install icons for different devices, however, I left it blank for simplicity.

Now, when Svelte builds your app, it will read these files and create a running service worker that caches a navigation resource whenever offline activity is detected. Running `npm run dev` will start your app with the service worker. If you open DevTools in Chrome, you’ll see the service worker starting:

![Svelte App Run Service Worker](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-app-run-service-worker.png)

To see the service worker in action, you can also go into the `network` tab and move your session offline:

![Svelte Service Worker Offline](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-service-worker-offline.png)

![Svelte Final Service Worker](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-final-service-worker.png)

## Build a PWA in SvelteKit

Now, let’s create a PWA using SvelteKit. For this example, we’ll create a “Hello, World!” application. Run the code below in your terminal:

```bash
npm init svelte@next sveltekit-pwa
```

The CLI will ask you if you’d like to create a demo app or a skeleton project. Select **demo app**:

![Svelte Sapper CLI Demo App](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-sapper-cli-demo-app.png)

The CLI will also ask you if you’d like to use TypeScript, Prettier, or ESLint. Add the configuration below:

![Svelte Sapper TypeScript Prettier Eslint Configuration](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-sapper-typescript-prettier-eslint-configuration.png)

Now, head into your SvelteKit project directory and install the required dependencies with the following code:

```bash
cd sveltekit-pwa
npm install
```

To run your project, add the code below:

```bash
npm run dev -- --open
```

![New SvelteKit App Homepage](https://blog.logrocket.com/wp-content/uploads/2021/09/new-sveltekit-app-homepage.png)

Now that our project is created, we can make it a PWA using the same steps we followed to create a basic Svelte PWA. Create a `manifest.json` file in the `static` directory:

![SvelteKit PWA Manifest Json](https://blog.logrocket.com/wp-content/uploads/2021/09/sveltekit-pwa-manifest-json.png)

Next, modify the `app.html` file in the `src` directory to include a reference to the `manifest.json` file:

![Reference Manifest Json File](https://blog.logrocket.com/wp-content/uploads/2021/09/reference-manifest-json-file.png)

Lastly, create a `service-worker.js` file in the `src` directory:

```javascript
import { build, files, timestamp } from '$service-worker';
const worker = (self as unknown) as any;
const FILES = `cache${timestamp}`;
const to_cache = build.concat(files);
const staticAssets = new Set(to_cache);
// listen for the install events
worker.addEventListener('install', (event) => {
    event.waitUntil(
        caches
            .open(FILES)
            .then((cache) => cache.addAll(to_cache))
            .then(() => {
                worker.skipWaiting();
            })
    );
});
// listen for the activate events
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();
        })
    );
});
// attempt to process HTTP requests and rely on the cache if 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;
    }
}
// listen for the fetch events
worker.addEventListener('fetch', (event) => {
    if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
    const url = new URL(event.request.url);
    // only cache files that are local to your application
    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);
            })()
        );
    }
});
```

With our SvelteKit PWA finished, we can connect it to the internet and see it running:

![SvelteKit PWA Internet Connection](https://blog.logrocket.com/wp-content/uploads/2021/09/sveltekit-pwa-internet-connection.png)

Without internet connection, our app will look like the image below:

![SvelteKit PWA Without Internet](https://blog.logrocket.com/wp-content/uploads/2021/09/sveltekit-pwa-without-internet.png)

Despite the obvious HTTP errors, the app is able to gracefully handle being disconnected from the internet.

## Build a PWA in Sapper

> As stated in the [official Sapper docs](https://sapper.svelte.dev/docs/#Introduction): Sapper’s succesor, SvelteKit, is currently available for use. All development efforts moving forward will be focused on SvelteKit. The Sapper docs include a helpful [migration guide](https://sapper.svelte.dev/migrating/). If you prefer to build you application with Sapper despite the lack of support, read ahead:

Sapper allows you to develop your app as you would with Svelte, however, it has the added benefit of support for PWAs. Create a Sapper application by running the following code in your terminal:

```bash
npx degit "sveltejs/sapper-template#rollup" sapper-pwa
```

Install the required dependencies by running the code below:

```bash
cd sapper-pwa
npm install
```

Now, to see your Sapper application in action, add the local run command as follows:

```bash
npm run dev</pre>
```

![Svelte Sapper PWA Template](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-sapper-pwa-template.png)

Looking at the generated code, you’ll see that the project already includes a service worker and a manifest file:

![Svelte Sapper Service Worker Manifest File](https://blog.logrocket.com/wp-content/uploads/2021/09/svelte-sapper-service-worker-manifest-file.png)

With this process, you can basically develop a Sapper application just like you would with Svelte.

## Conclusion

In this article, we explored the benefits of building PWAs and examined how they are built using service workers and web manifests. Then, we explored three methods for building PWAs using Svelte, SvelteKit, and Sapper.

PWAs are likely going to continue growing in use due to the popularity of native applications. Thankfully, building a PWA is fairly straightforward when you use a modern framework like Svelte. I hope you enjoyed this tutorial!

---

## [LogRocket](https://logrocket.com/signup/): Full visibility into your web apps

[![LogRocket Dashboard Free Trial Banner](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

[Try it for free](https://logrocket.com/signup/).