Zima Weather: Service Workers

Zima Weather: Service Workers

First, let's get a solid understanding of how resource cache will work without any service workers. For my experiments, I use a translation json `translation.json` that is loaded via i18next.js library.

Local web server

For local testing, I will use a simple http-server from npm.

Initial Resource load

We clear all caches in Chrome, and perform initial load:

By default, local http-server uses max-age=3600, so the resource is cached for exactly one hour.

Change Resource

Let's now change a string in the translations.json:

As expected, changes are not reflected, resource is loaded from disk cache, and it won't be updated for the next hour.

Cloudflare

Now let's repeat same experiments on CloudFlare.

We should be careful though, as we can't use pages.dev domain. It sets max-age=0 hence resources are always requested through the network.

Initial Resource load

Okay, CloudFlare does not cache .json files by default, hence max-age=0, and the resource will be always updated, but for other resources like images, CSS and JS files, it will behave in the same way as the local server.

So let's proceed now to implement a simple Service Worker using the workbox-build package from Google.

Service Workers

Here is the actual service worker code for Zima Weather:

import {generateSW} from 'workbox-build';

await generateSW({
    skipWaiting: true,
    globDirectory: 'dist/',
    globPatterns: [
        '**/*.{png,json,woff2,svg,css,js}'
    ],
    globIgnores: [...],
    swDest: 'dist/sw.js',
    ignoreURLParametersMatching: [
        /^utm_/,
        /^fbclid$/,
        /^source$/
    ],
    // Adding runtimeCaching configuration
    runtimeCaching: [
        {
            urlPattern: new RegExp('/locales/.*\\.json$'),
            handler: 'NetworkFirst',
            options: {
                cacheName: 'translation-cache',
            },
        },
        {
            urlPattern: /\.(?:html|svg|json|png)$/,
            handler: 'StaleWhileRevalidate',
            options: {
                cacheName: 'assets-cache',
            },
        },
    ],
});

There are many parameters here, but the important bit here is the runtimeCaching configuration. We configure two caches with different strategies:

  • translation-cache uses NetworkFirst strategy, which means it will always try to load the resources over the network first. NB: urlPattern regex is matching against full urls like http://localhost:8080/path, and not just relative path, so don't use ^ in regex unless it's a full url.
  • Other resources use StaleWhileRevalidate strategy – the resource will first be used from cache, and revalidated in background.

Local web server

I will continue using only local server for the rest of the post since the behavior is basically the same as CloudFlare. I will disable disk cache, however, such that it does not interfere with the service worker strategies. It's a bit confusing, but NetworkFirst strategy will actually forward request to the browser, and it might serve it from the cache if it is valid. Here is the full request workflow with a service worker:

Source: https://web.dev/service-worker-caching-and-http-caching/

NetworkFirst Strategy

Let's see the Network-first strategy in action first. We clean everything and load the page again with service worker:

Okay, service worker is handling our request, and since we don't have any cache, it loads it from the network. I now update the resource and refresh the page:

Okay, the service worker successfully loaded the resource from the network again.

StaleWhileRevalidate Strategy

I will now remove the NetworkFirst strategy handler and let the StaleWhileRevalidate handler process the request. Initial load:

I change the resource, and reload the page:

I see as if the service worker has already reloaded the resource, but it actually did it after it returned an existing cached version to the app. I can see it because I get the new translation string only after second reload: