Skip to content

Theme docs

PWA

The service worker, and when not to turn it on.

The theme ships a service worker and a web app manifest. Both are off by default, and that is a recommendation as much as a setting.


Read this before turning it on

A service worker is a piece of code that sits between your readers and your server, permanently, for as long as their browser keeps it. When it works, pages load instantly and survive a lost connection. When it is misconfigured, it serves someone a version of your site from three deploys ago and there is nothing they can do about it except clear site data — which they will not know to do.

Turn it on when:

  • your domain is settled (a service worker is scoped to an origin)
  • the site is deployed and stable
  • you actually want offline reading

Do not turn it on to tick a Lighthouse box.


Turning it on

pwa:
  enabled: true
  name: Your Site
  short_name: Site
  theme_color: "#0176d3"
  background_color: "#ffffff"
  display: standalone
  precache:
    - /
    - /blog/
    - /offline/

Keep /offline/ in precache — it is the fallback shown when a reader is offline and asks for a page they have never visited.

Registration is additionally gated on production, so the worker never installs during local development. To test it: JEKYLL_ENV=production bundle exec jekyll build && bundle exec jekyll serve --skip-initial-build.


What the worker does

Three strategies, chosen so the failure mode is “slightly stale” rather than “badly wrong”:

Resource Strategy Why
HTML Network first, cache as fallback Online readers always get the current page; offline readers get the last one they saw
CSS, JS Stale-while-revalidate Instant, refreshed in the background
Images, fonts Cache first They do not change under the same URL

Only same-origin GET requests are touched. A cached POST is meaningless, and caching another origin’s responses is not this worker’s business.

Invalidation

The cache name carries a build timestamp, so every deploy creates a new cache and the activate step deletes the old ones. That is the whole strategy, and it is deliberately blunt: subtle cache invalidation is how service workers go wrong.

A returning reader gets the new version on their second navigation after a deploy — the first one is served from cache while the update installs.


Icons

The manifest points at three PNGs in assets/img/:

File Size Purpose
icon-192.png 192×192 Standard
icon-512.png 512×512 Standard, splash screen
icon-maskable.png 512×512 Maskable — Android crops this to a circle

The maskable one has its artwork inset into the safe zone, because Android will crop the outer 20% away. Using the same image for both is why so many installed web apps have their logo clipped.

To rebrand them, replace the four PNGs with your own — any image tool works. Keep the maskable one’s artwork inside the middle 60% of the canvas; the launcher crops the rest.


Turning it off again

This is the part people get stuck on. Setting enabled: false stops the theme registering the worker — but it does not remove the one already installed in your readers’ browsers. That worker keeps serving its cache.

To actually retire it, deploy a sw.js that unregisters itself before you remove the file:

self.addEventListener('install', function () { self.skipWaiting(); });

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys()
      .then(function (keys) { return Promise.all(keys.map(function (k) { return caches.delete(k); })); })
      .then(function () { return self.registration.unregister(); })
      .then(function () { return self.clients.matchAll(); })
      .then(function (clients) { clients.forEach(function (c) { c.navigate(c.url); }); })
  );
});

Leave that in place for a few weeks — long enough for returning readers to pick it up — then delete sw.js.


The offline page

/offline/ is a normal page in the repository. Edit it like any other. It is noindex, kept out of the sitemap and out of the search index, because it is a shell rather than content.

Found something wrong on this page? The source lives in docs/ — corrections welcome, see the contribution guide.

to move to open esc to close Full search