4 minutes reading time
Updated 02 Apr 2024
I am not a fan of JavaScript. But I already started with some Service Worker examples from Mozilla some time ago, and PWAs have proven to be very effective. So, let's go.
My first implementation was pulling cache lists from a dedicated page generated by the Zola template using a macro that pulls assets from taxonomies, pages, etc. But besides the need for filtering and discarding a lot of data, having a dedicated page for this is just ugly. That was the only way to make it work with fetch()
. And I had to add an extra zola build
as well.
Zola does not yet have the capability to populate non-HTML files, and I could not justify adding extra steps with NPM/etc. just for a single service worker event to function. So a new approach was needed.
Workbox or sw-tools libraries would resolve probably everything, but it's too easy. Since I would have to maintain JavaScript anyway, let's get on with it.
Huh
The solution is a cache-first service worker strategy with a fallback to offline mode. This feels like the most efficient approach. And it requires no external dependencies or extra steps. I could play with network requests, but timeout
sounds too slow already, so maybe next time.
I decided to remove the hardcoded/dynamic cache list and install a fallback page instead.
oninstall =;
The rest is cached "as you go"—the service worker filters useful requests and writes them into the cache. This way, I save critical resources during the first page navigation, and there is no funny business. If the requests fail (no network), an offline page is served.
onfetch =;
The site's static assets are hashed by my Zola theme, so the strategy fits perfectly.
Housekeeping is done via cacheName
- all previous (old) cache records are purged during the service worker's activation, maintaining a clean browser environment.
onactivate =;
Although, I want to find a nice way to "expire" cache records, relying on a hardcoded cache name only might be an issue.
To handle "expired" resources, I switched the fetch
event to the stale-while-revalidate strategy:
onfetch =;
After settling on the cache event, I wanted to properly support the offline mode. The standard approach for this is to use the background sync API. A quick examination suggests this is a picky solution, and support is very limited. That alone is enough to look for a workaround. I started from the ground.
First, I needed to generate the cache list, so I took my macro and applied its logic directly in the HTML <head>
to use the output with a data-cache
tag attribute while linking the service worker's loader script.
Second, I needed a way to get the cache list to "sync" with the service worker. The search got me the postMessage()
service worker method that "sends a message to the worker". Bingo. To catch the message on the other side, one needs to implement the message
event:
onmessage =;
Now, what stops me from repeating what I have been doing during the service worker installation? I sent a message after the service worker's activation, checked the request type, and started to fill the cache using URLs from the message. Worked.
onmessage =;
The cache is full, all assets are included, and I had no issues mixing absolute/relative links (though maybe it's not a good "feature" after all). The hardcoded cache list with critical assets got reintroduced, along with the offline page, all to be cached during the installation. I also started requesting the precache only after the installation, to avoid redundant fetches:
;
;
;
;
This setup delivers a fully offline-ready site. The service worker deploys critical files during the installation, then precaches everything else.
It looks like I'll do anything just to avoid touching CSS in Halve-Z. It was a nice exercise, though. I built a simple and capable PWA without jeopardizing the workflow or user experience. All code is available in theme's pull requests #22, #23, and #24.