Navigation requests are requests for HTML documents made by your browser whenever you enter a new URL in the navigation bar, or follow a link on a page taking you to a new URL. This is where service workers make their biggest impact on performance: if you use a service worker to respond to navigation requests without waiting for the network, you can ensure that navigations are reliably fast, in addition to being resilient when the network is unavailable. This is the single biggest performance win that comes from a service worker, versus what's possible with HTTP caching.
As detailed in the Identify resources loaded from the network guide, a navigation request is the first of potentially many requests made in the "waterfall" of network traffic. The HTML that you load via a navigation request kicks off the flow of all other requests for subresources like images, scripts, and styles.
Inside of a service worker's
fetch event handler, you can determine whether a request is a
navigation by checking the
request.mode property on the
FetchEvent. If it's set to
then it's a navigation request.
As a general rule, do not use long-lived
Cache-Control headers to cache
the HTML response for a navigation request. They should normally be satisfied via the network, with
Cache-Control: no-cache, to ensure that the HTML, along with the chain of subsequent
network requests, is (reasonably) fresh. Going against the network each time the user navigates to a
new page unfortunately means that each navigation might be slow. At the very least, it
means that it won't be reliably fast.
Cache-Control: no-cache means the browser must check (or "revalidate") with the server before
using a previously cached resource. This requires a round-trip network communication to complete
before the resource can be used.
Different approaches for architectures
Figuring out how to respond to navigation requests while avoiding the network can be tricky. The right approach depends very much on your web site's architecture and the number of unique URLs that users might navigate to.
While there's no one-size-fits all solution, the following general guidelines should help you decide which approach is the most viable.
Small static sites
If your web app consists of a relatively small number (think: a couple of dozen) unique URLs, and each of those URLs corresponds to a different static HTML file, then one viable approach is to just cache all of those HTML files, and respond to navigation requests with the appropriate cached HTML.
Using precaching, you can cache the HTML in advance, as soon as the service worker is installed, and update the cached HTML each time you rebuild your site and redeploy your service worker.
Alternatively, if you would rather avoid precaching all of your HTML—perhaps because users tend to navigate to only a subset of URLs on your site—you can use a stale-while-revalidate runtime caching strategy. Be careful about this approach, though, as each individual HTML document is cached and updated separately. Using runtime caching for HTML is most appropriate if you have a small number of URLs that are revisited frequently by the same set of users, and if you feel comfortable about those URLs being revalidated independently of each other.
Workbox provides the tools that you need to implement this approach; the
allows you to specify which HTML document to use as your app shell, along with an optional allow and
deny list to limit this behavior to a subset of your URLs.
If your web server generates your site's HTML dynamically, or if you have more than a few dozen unique pages, then it's much harder to avoid the network when handling navigation requests. The advice in Everything else will likely apply to you.
If your web server falls into that category and you would like to explore one approach to moving HTML generation off the network and into your service worker, the guidance in Beyond SPAs: alternative architectures for your PWA can get you started.
If you can't respond to navigation requests with cached HTML, you must take steps to ensure that
adding a service worker to your site (to handle other, non-HTML requests) doesn't end up slowing
down your navigations. Starting up the service worker without using it to respond to a navigation
request will introduce a small amount of latency (as explained in Building Faster, More Resilient
Apps with Service Worker). You can mitigate this overhead by enabling
a feature called navigation
preload, and then using the
that's been preloaded inside of your
fetch event handler.
Workbox provides a helper library that feature-detects whether navigation preload is supported, and if so, simplifies the process of telling your service worker to use the network response.