Precaching dos and don'ts

This documentation has covered precaching previously, but not enough about how to get it right. This is important, because regardless of whether you use Workbox, it's easy to precache too much and potentially waste data and bandwidth. You should be careful about how your precaching payload affects the user experience.

As you read this document, understand that these are general guidelines. Your application architecture and requirements may require you to do things differently than suggested here, but these guidelines serve as good defaults.

Do: precache critical static assets

The best candidates for precaching are critical static assets, but what counts as a "critical" asset? From a developer perspective, it may be tempting to think of an entire application as "critical", but the user's perspective is what matters most. Think of critical assets as those utterly necessary to provide a user experience:

  • Global stylesheets.
  • JavaScript files that provide global functionality.
  • Application shell HTML, if that applies to your architecture.

Reminder: these are general guidelines, not hard recommendations. When precaching assets, it's best to err on the side of precaching less rather than more.

Do: precache an offline fallback for multipage websites

For typical multipage websites, you might be relying on a network-first or network-only caching strategy to deal with navigation requests.

In such cases, you'll want to ensure your service worker precaches and responds with an offline fallback page when the user makes a navigation request while offline. One way to do this in Workbox might be to use a network-only strategy with an offline fallback, taking advantage of navigation preload as well:

import {PrecacheFallbackPlugin, precacheAndRoute} from 'workbox-precaching';
import {registerRoute, Route} from 'workbox-routing';
import {NetworkOnly} from 'workbox-strategies';
import * as navigationPreload from 'workbox-navigation-preload';

navigationPreload.enable();

// Ensure that /offline.html is part of your precache manifest!
precacheAndRoute(self.__WB_MANIFEST);

// The network-only callback should match navigation requests, and
// the handler for the route should use the network-only strategy, but
// fall back to a precached offline page in case the user is offline.
const networkOnlyNavigationRoute = new Route(({request}) => {
  return request.mode === 'navigate';
}, new NetworkOnly({
  plugins: [
    new PrecacheFallbackPlugin({
      fallbackURL: '/offline.html'
    })
  ]
}));

registerRoute(networkOnlyNavigationRoute);

This ensures that if a user goes offline and navigates to a page that isn't in their cache, they'll at least get some offline content.

Maybe do: consider speculative precaching

That's a big "maybe" up there, but there's a potential benefit in precaching assets that are only used under certain scenarios. Think about it this way: users will incur some extra upfront data downloads, with the speculative benefit of speeding up future requests for those assets.

Now for the big caveat: be very careful if you decide to do this. It's easy to waste data doing this, and it should be a data-driven decision. Additionally, avoid speculatively precaching assets that change frequently, as the user will incur additional data usage each time the precaching code detects a new revision. Observe user flows in your analytics to see where users tend to go. If you have any doubts about speculatively precaching assets, that's probably a good sign not to do it.

Maybe don't: precache static HTML

This guideline applies more to static sites where discrete HTML files are either generated by a static site generator or manually created, instead of being dynamically generated or provided by an application back end. If this describes your architecture, then it's probably best if you don't precache every HTML file for your website.

One problem with precaching an entire site's worth of HTML files is that markup that gets precached now will always be served from the cache later until the service worker is updated. This is great for performance, but can lead to significant cache churn if your website's HTML changes frequently.

There's a couple of exceptions to this rule, though. If you're deploying a small website with a few static HTML files, it's probably fine to precache all of those pages so that they'll be available offline. If you have an especially large website, consider speculatively precaching a few high value pages and an offline fallback, and rely on runtime caching to cache other pages for you.

Don't: precache responsive images or favicons

This is less of a general guideline and more of a rule. Responsive images are a complex solution for a complex problem: you have many users on many devices, each varying in screen size, pixel density, and support for alternative formats. If you precache an entire set of responsive images, you're probably precaching several images when the user may only ever end up downloading one of them.

Favicons present a similar situation, in that websites often deploy an entire set of favicons for different scenarios. Most often, only one favicon gets requested, making precaching an entire favicon set similarly wasteful.

Do your users a favor and don't precache responsive image and favicon sets. Rely on runtime caching instead. If you must precache images, precache widely-used images that aren't part of a set of responsive images or favicons. SVGs are less risky in terms of data usage, a single SVG renders optimally regardless of a given screen's pixel density.

Don't: precache polyfills

Varying browser support for APIs is a persistent challenge for web developers, and polyfilling is one of the ways that challenge is met. One way to minimize the performance cost of polyfills is to do feature checking and only load polyfills for the browsers that need them.

Because conditionally loading polyfills happens during runtime with respect to the current environment, precaching polyfills is a gamble. Some users will benefit from it, while others will end up wasting bandwidth for unnecessary polyfills.

Don't precache polyfills. Rely on runtime caching to ensure they get cached only in browsers that require them to avoid wasting data.

Conclusion

Precaching requires some forethought on what assets your users actually need ahead of time, but you can definitely get it right in a way that prioritizes future performance and reliability.

If you're unsure about whether certain assets should be precached, your best bet might be to tell Workbox to exclude those assets and create a runtime caching route to handle them. Either way, precaching is covered in detail later in this documentation, so you'll be able to apply these principles to your precaching logic in the future.