Skip to main content

Runtime caching

Original source (Apache 2.0 License). Adapted for Serwist’s usage.

Introduction

Some assets in your web application may be infrequently used, very large, or vary based on the user’s device (such as responsive images) or language. These are instances where precaching may be an anti-pattern, and you should rely on runtime caching instead.

Caching strategies

Serwist provides these strategies out of the box:

  • StaleWhileRevalidate — Uses a cached response for a request if it’s available and updates the cache in the background with a response from the network. Therefore, if the asset isn’t cached, it will wait for the network response and use that. It’s a fairly safe strategy, as it regularly updates cache entries that rely on it. The downside is that it always makes a network request in the background.
  • NetworkFirst — Tries to get a response from the network first. If a response is received, it passes that response to the browser and saves it to a cache. If the network request fails, the last cached response will be used, enabling offline access to the asset.
  • CacheFirst — Checks the cache for a response first and uses it if available. If the request isn’t in the cache, the network is used and any valid response is added to the cache before being passed to the browser.
  • NetworkOnly — Forces the response to come from the network.
  • CacheOnly — Forces the response to come from the cache.

You may also write your own strategy as detailed in Caching strategies: Creating a new strategy.

Applying caching strategies with route matching

The Serwist class exposes the registerCapture method to allow you to match a pattern of routes to a caching strategy. registerCapture accepts two arguments:

  • A string, regular expression, or a match callback to specify route matching criteria.
  • A handler for the route — typically a built-in Strategy.

You may also use the runtimeCaching option of Serwist’s constructor. It is essentially a syntactic sugar for registerCapture.

When the service worker intercepts a network request, Serwist tries to match the URL of the request to one of the registered handlers, which will then be used to generate a response. In the following example, we register a route that matches incoming same-origin image requests, applying the CacheFirst strategy.

import { CacheFirst, Serwist } from "serwist";

const serwist = new Serwist();

serwist.registerCapture(({ request, sameOrigin }) => {
  return sameOrigin && request.destination === "image";
}, new CacheFirst());

serwist.addEventListeners();

Using multiple caches

Serwist allows you to bucket cached responses into separate Cache instances using the cacheName option available in the built-in strategies. In the following example, images use the StaleWhileRevalidate strategy, whereas CSS and JavaScript assets use the CacheFirst strategy. The route for each asset places responses into separate caches, by adding the cacheName property.

import { CacheFirst, StaleWhileRevalidate, Serwist } from "serwist";

const serwist = new Serwist({
  runtimeCaching: [
    // Handle images
    {
      matcher({ request }) {
        return request.destination === "image";
      },
      handler: new StaleWhileRevalidate({
        cacheName: "images",
      }),
    },
    // Handle scripts
    {
      matcher({ request }) {
        return request.destination === "script";
      },
      handler: new CacheFirst({
        cacheName: "scripts",
      }),
    },
    // Handle styles
    {
      matcher({ request }) {
        return request.destination === "style";
      },
      handler: new CacheFirst({
        cacheName: "styles",
      }),
    },
  ],
});

serwist.addEventListeners();
Example using multiple caches

Cross-origin considerations

The interaction of your service worker with cross-origin assets is considerably different from with same-origin assets. Cross-Origin Resource Sharing (CORS) is complicated, and that complexity extends to how you handle cross-origin resources in a service worker.

Opaque responses

When making a cross-origin request in no-cors mode, the response can be stored in a service worker cache and even be used directly by the browser. However, the response body itself can’t be read via JavaScript. This is known as an opaque response.

Opaque responses are a security measure intended to prevent the inspection of a cross-origin asset. You can still make requests for cross-origin assets and even cache them, you just can’t read the response body or even read its status code!

Remember to opt into CORS mode

Even if you load cross-origin assets that do set permissive CORS headers that allow you read responses, the body of cross-origin response may still be opaque. For example, the following HTML will trigger no-cors requests that will lead to opaque responses regardless of what CORS headers are set:

<link rel="stylesheet" href="https://example.com/path/to/style.css">
<img src="https://example.com/path/to/image.png">

To explicitly trigger a cors request that will yield a non-opaque response, you need to explicitly opt-in to CORS mode by adding the crossorigin attribute to your HTML:

<link crossorigin="anonymous" rel="stylesheet" href="https://example.com/path/to/style.css">
<img crossorigin="anonymous" src="https://example.com/path/to/image.png">

This is important to remember when your service worker cache subresources loaded at runtime.

Serwist may not cache opaque responses

By default, Serwist takes a cautious approach to caching opaque responses. As it’s impossible to examine the status code of opaque responses, caching an error response can result in a persistently broken experience if a cache-first or cache-only strategy is used.

If you need to cache an opaque response in Serwist, you should use a network-first or stale-while-validate strategy to handle it. This ensures that failed responses won’t persist, and will eventually be replaced by usable responses.

If you use another caching strategy and an opaque response is returned, Serwist will warn you that the response wasn’t cached when in development mode.

If you are absolutely sure that you want Serwist to cache an opaque response using a cache-first or cache-only strategy, you can do so using CacheableResponse or CacheableResponsePlugin.

Opaque responses’ size padding

To avoid leakage of cross-domain information, there’s a significant padding added to the size of an opaque response when calculating storage quota limits. This affects how navigator.storage reports storage quotas.