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();
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.