Basics of Service Workers

What Is a Service Worker?

A service worker is a JavaScript file that runs separately from a web page.

It can:

Browser


Service Worker

   ├── Cache

   └── Network

Registering a Service Worker

In your webpage:

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/sw.js");
}

This tells the browser to register /sw.js.


Basic Service Worker

self.addEventListener("install", () => {
  console.log("Service worker installed");
});

self.addEventListener("activate", () => {
  console.log("Service worker activated");
});

self.addEventListener("fetch", (event) => {
  console.log("Request:", event.request.url);
});

There are three important events:

Event Purpose
install Initial setup
activate Cleanup / becoming active
fetch Intercept network requests

Service Worker Lifecycle

register()


 install


 waiting


 activate


 controlling pages

A service worker does not immediately control the page that registered it.

Usually, a reload is needed after the first installation.


Caching Files

The Cache API can store resources:

const CACHE_NAME = "v1";

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(["/", "/index.html", "/style.css", "/app.js"]);
    }),
  );
});

Now those files can be available offline.


Serving Cached Files

A simple cache-first strategy:

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches.match(event.request).then((cached) => {
      return cached || fetch(event.request);
    }),
  );
});

Behavior:

Request


Cache?
 ┌─┴─┐
Yes  No
 │    │
 ▼    ▼
Cache Network


   Response

Offline Fallback

You can provide a special offline page:

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches.open("v1").then((cache) => {
      return cache.add("/offline.html");
    }),
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    fetch(event.request).catch(() => {
      return caches.match("/offline.html");
    }),
  );
});

If the network fails, /offline.html is returned.


Updating a Cache

Use cache versions:

const CACHE_NAME = "v2";

Then delete old caches:

self.addEventListener("activate", (event) => {
  event.waitUntil(
    caches
      .keys()
      .then((keys) =>
        Promise.all(
          keys
            .filter((key) => key !== CACHE_NAME)
            .map((key) => caches.delete(key)),
        ),
      ),
  );
});

Service Worker Scope

A service worker controls URLs under its scope.

For example:

/sw.js

normally controls:

/
├── index.html
├── about.html
└── app/
    └── page.html

But:

/sw.js

does not normally control something outside its scope.


HTTPS Requirement

Service workers generally require a secure context.

That means:

https://example.com

works.

For development:

http://localhost:3000

is also allowed.


Service Workers Are Not Web Workers

Both run JavaScript outside the normal page execution context, but they have different purposes.

Web Worker

Used for background computation:

Page

 └── Web Worker
       └── heavy computation

Service Worker

Used for network/resource control:

Page

 └── Service Worker
       ├── Cache
       ├── Network
       └── Offline

Common Cache Strategies

Cache First

Cache → Network

Good for:

Network First

Network → Cache

Good for:

Stale While Revalidate

Cache → return immediately
Network → update cache

Good for:


Service Workers and PWAs

A service worker is one of the major pieces of a Progressive Web App.

A PWA commonly includes:

Website
├── HTTPS
├── Service Worker
├── Web App Manifest
└── Cached resources

The service worker is what makes reliable offline behavior possible.


Service Worker Limitations

Service workers:

The page and service worker communicate using APIs such as:

navigator.serviceWorker.controller;

and:

postMessage();

Minimal Offline Website

A basic structure:

my-site/
├── index.html
├── offline.html
├── style.css
├── app.js
└── sw.js

index.html:

<script>
  navigator.serviceWorker.register("/sw.js");
</script>

sw.js:

const CACHE = "site-v1";

self.addEventListener("install", (event) => {
  event.waitUntil(
    caches
      .open(CACHE)
      .then((cache) =>
        cache.addAll([
          "/",
          "/index.html",
          "/offline.html",
          "/style.css",
          "/app.js",
        ]),
      ),
  );
});

self.addEventListener("fetch", (event) => {
  event.respondWith(
    caches
      .match(event.request)
      .then((response) => response || fetch(event.request)),
  );
});

Now previously cached resources can be loaded without a network connection.


Key Idea

A service worker sits between your website and the network:

             ┌─────────┐
             │ Website │
             └────┬────┘


          ┌───────────────┐
          │Service Worker │
          └───────┬───────┘

             ┌────┴────┐
             ▼         ▼
          Cache     Network

The main reason to use one is control over network requests and offline behavior.