Dev Channel
Published in

Dev Channel

Service Worker Caching Strategies Based on Request Types

TL;DR

Different Caching Strategies for Different Types of Resources

URL-based Determination of the Request Type

// In serviceworker.js
self.addEventListener('fetch', (event) => {
// Parse the URL
const requestURL = new URL(event.request.url);
// Handle article URLs
if (/^\/article\//.test(requestURL.pathname)) {
event.respondWith(/* some response strategy */);
return;
}
if (/\.webp$/.test(requestURL.pathname)) {
event.respondWith(/* some other response strategy */);
return;
}
/* … */
});

This approach allows developers to deal with their WebP images (i.e., requests that match the regular expression /\.webp$/) differently than with their HTML articles (i.e., requests that match /^\/article\//). The downside of this approach is that it makes hard-coded assumptions about the URL structure of a PWA or the used MIME types’ file extensions, which creates a tight coupling between app and service worker logic. Should you move away from WebP to a future superior image format, you would need to remember to update your service worker’s logic as well.

Request.destination-based Determination of the Request Type

“A request has an associated destination, which is the empty string, "audio", "audioworklet", "document", "embed", "font", "image", "manifest", "object", "paintworklet", "report", "script", "serviceworker", "sharedworker", "style", "track", "video", "worker", or "xslt". Unless stated otherwise it is the empty string.”

The empty string default value is the biggest caveat. Essentially, you can’t determine the type of resources that are requested via the following methods:

navigator.sendBeacon(), EventSource, HTML’s <a ping=""> and <area ping="">, fetch(), XMLHttpRequest, WebSocket, [and the] Cache API

In practice having Request.destination get set to the non-informative empty string default value matters the most for fetch() and XMLHttpRequest, so at least for resources requested through these techniques, it’s oftentimes back to URL-based pattern handling inside your service worker.

On the bright side, you can determine the type of everything else perfectly fine. I have built a little Request.destinationplayground app that shows some of these destinations in action. Note that for the sake of the to-be-demonstrated effect it also contains some anti-patterns like registering the service worker as early as possible and actively circumventing the browser’s preloading heuristics (never do this in production).

Request.destination playground app showing different request types

When you think about it, there are a huge number of ways a page can request resources to load. A <video> can load an image as its poster frame and a timed text track file via <track>, apart from the video bytes it obviously loads. A stylesheet can cause images to load that are used somewhere on the page as background images, as well as web fonts. An <iframe> loads an HTML document. Oh, and the HTML document itself can load manifests, stylesheets, scripts, images, and a ton of other elements like <object> that was quite popular in the past to load Flash movies.

An <img>, two <p>s with background images and triggers for XMLHttpRequest or fetch(), an <iframe>, and a <video> with poster image and timed text track

Coming back to the initial example of the shopping PWA, we could come up with a simple service worker router as outlined in the code below. This router is completely agnostic of the URL structure, so there’s no tight coupling at all.

// In serviceworker.js
self.addEventListener('fetch', (event) => {
const destination = event.request.destination;
switch (destination) {
case 'style':
case 'script':
case 'document':
case 'image': {
event.respondWith(
/* "Network Falling Back to Cache" strategy */);
return;
}
case 'font': {
event.respondWith(/* "Cache Only" strategy */);
return;
}
// All `XMLHttpRequest` or `fetch()` calls where
// `Request.destination` is the empty string default value
default: {
event.respondWith(/* "Network Only" strategy */);
return;
}
}
});

Browser Support for Request.destination

When Request.destination isn’t Enough

Fortunately, you can freely combine Request.destination with URL-based pattern matching, there’s absolutely no harm in doing so. A basic example could be to use Request.destination for dealing with all kinds of images to return a default offline fallback placeholder, and to use Request.url with URL-based pattern matching for other resources. You can likewise decide to have different behavior based on the Request.mode of the request, for instance to check if you are dealing with a navigational request (Request.mode === 'navigate') in single-page apps.

Conclusion

Acknowledgements

--

--

Developers Channel - the thoughts, opinions and musings from members of the Chrome team.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store