Skip to main content

Getting started

This guide uses React, but it applies to any project using Vite. For some frameworks, we also have recipes tailored to them:

Install

Run the following command:

npm i -D @serwist/vite @serwist/window serwist

Implementation

Step 1: Add Serwist’s Vite plugin

Update or create your Vite configuration file with the following content:

import { serwist } from "@serwist/vite";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    serwist({
      swSrc: "src/sw.ts",
      swDest: "sw.js",
      globDirectory: "dist",
      injectionPoint: "self.__SW_MANIFEST",
      rollupFormat: "iife",
    }),
  ],
});

Step 2: Update tsconfig.json

If you use TypeScript, you should add the following content to tsconfig.json in order to get the correct types:

{
  // Other stuff...
  "compilerOptions": {
    // Other options...
    "types": [
      // Other types...
      // This allows Serwist to properly type "virtual:serwist".
      "@serwist/vite/typings"
    ],
    "lib": [
      // Other libs...
      // Add this! Doing so adds WebWorker and ServiceWorker types to the global.
      "webworker"
    ],
  },
}

Otherwise, safely skip this step.

Step 3: Update .gitignore

If you use Git, update your .gitignore like so:

# Serwist
public/sw*
public/swe-worker*

Otherwise, safely skip this step.

Step 4: Create a service worker

Basic service worker template to get Serwist up and running:

import { defaultCache } from "@serwist/vite/worker";
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
import { Serwist } from "serwist";

// This declares the value of `injectionPoint` to TypeScript.
// `injectionPoint` is the string that will be replaced by the
// actual precache manifest. By default, this string is set to
// `"self.__SW_MANIFEST"`.
declare global {
  interface WorkerGlobalScope extends SerwistGlobalConfig {
    __SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
  }
}

declare const self: ServiceWorkerGlobalScope;

const serwist = new Serwist({
  precacheEntries: self.__SW_MANIFEST,
  skipWaiting: true,
  clientsClaim: true,
  navigationPreload: true,
  runtimeCaching: defaultCache,
});

serwist.addEventListeners();

Step 5: Add a web application manifest

Update public/manifest.json with the following content:

{
  "name": "My Awesome PWA app",
  "short_name": "PWA App",
  "icons": [
    {
      "src": "/icons/android-chrome-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "maskable"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "theme_color": "#FFFFFF",
  "background_color": "#FFFFFF",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait"
}

Step 6: Update your client entrypoint

Next up, update src/App.tsx with the following content:

import { getSerwist } from "virtual:serwist";
import { useEffect } from "react";

export default function App() {
  useEffect(() => {
    const loadSerwist = async () => {
      if ("serviceWorker" in navigator) {
        const serwist = await getSerwist();

        serwist?.addEventListener("installed", () => {
          console.log("Serwist installed!");
        });

        void serwist?.register();
      }
    };

    loadSerwist();
  }, []);

  return <></>;
}

Step 7: Add metadata

Add the following content to your HTML file:

<head>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>My awesome PWA app</title>
  <meta name="description" content="Best PWA app in the world!">
  <link rel="shortcut icon" href="/favicon.ico">
  <link rel="mask-icon" href="/icons/mask-icon.svg" color="#FFFFFF">
  <meta name="theme-color" content="#ffffff">
  <link rel="apple-touch-icon" href="/icons/touch-icon-iphone.png">
  <link rel="apple-touch-icon" sizes="152x152" href="/icons/touch-icon-ipad.png">
  <link rel="apple-touch-icon" sizes="180x180" href="/icons/touch-icon-iphone-retina.png">
  <link rel="apple-touch-icon" sizes="167x167" href="/icons/touch-icon-ipad-retina.png">
  <link rel="manifest" href="/manifest.json">
  <meta name="twitter:card" content="summary">
  <meta name="twitter:url" content="https://yourdomain.com">
  <meta name="twitter:title" content="My Awesome PWA app">
  <meta name="twitter:description" content="Best PWA app in the world!">
  <meta name="twitter:image" content="/icons/twitter.png">
  <meta property="og:type" content="website">
  <meta property="og:title" content="My Awesome PWA app">
  <meta property="og:description" content="Best PWA app in the world!">
  <meta property="og:site_name" content="My awesome PWA app">
  <meta property="og:url" content="https://yourdomain.com">
  <meta property="og:image" content="/icons/og.png">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_2048.png" sizes="2048x2732">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_1668.png" sizes="1668x2224">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_1536.png" sizes="1536x2048">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_1125.png" sizes="1125x2436">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_1242.png" sizes="1242x2208">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_750.png" sizes="750x1334">
  <link rel="apple-touch-startup-image" href="/images/apple_splash_640.png" sizes="640x1136">
</head>