Why navigation requests deserve special treatment
When a user enters a URL or follows a link, the browser issues a navigation request for the HTML document. That document then triggers every subsequent request for subresources. Because the entire page load depends on this first step, it is the one place where a service worker can deliver its biggest performance gain: by answering navigation requests from the cache instead of waiting on the network.
A service worker fetch event handler can identify a navigation by checking request.mode === 'navigate'. That matters because the usual advice for HTTP caching—long-lived Cache-Control headers—does not apply well to HTML. For navigation requests, the recommended header is Cache-Control: no-cache, so the page and its request chain stay reasonably fresh. But going to the network on every navigation means each visit might be slow. It will not be reliably fast.
Choosing a strategy that fits your architecture
The right way to serve navigations without the network depends on how many unique URLs your site has and how its HTML is generated. A few general patterns cover most cases.
Caching for a small set of static pages
If your site has a couple dozen unique URLs, and each maps to a distinct static HTML file, precaching can handle all of them. The service worker caches every HTML file at install time and refreshes it whenever the site is rebuilt and redeployed.
An alternative is a stale-while-revalidate runtime strategy, which can be useful if users tend to revisit only a subset of URLs frequently. The trade-off is that each document is cached and revalidated on its own schedule, so this works best when you are comfortable with URLs being refreshed independently.
Serving the app shell for single-page apps
Single-page applications rely on client-side JavaScript and the History API to change the URL without a real navigation. The initial page load, however, is a real navigation, and it still must not block on the network.
The application shell model handles this cleanly: the service worker responds to any navigation request by returning one precached, bare-bones HTML file with a loading indicator or skeleton content. Existing client-side JavaScript then renders the appropriate view for the requested URL. Workbox's navigateFallback option implements this pattern, with optional allow and deny lists to scope it to specific URLs.
Rendering HTML inside the service worker
Sites with dynamic server-side HTML, or more than a few dozen pages, face a harder problem. One subset of multi-page apps may still avoid the network by replicating the server's HTML generation logic inside the service worker. That is most feasible when routing and templating code can be shared between the two environments—especially when the server uses JavaScript without Node.js-specific features like file system access. Chrome's Beyond SPAs write-up covers this approach in more detail.
When you must let the network respond
If cached HTML is not an option, adding a service worker for other request types can still introduce a small amount of navigation latency because the worker must start up. Two mitigations exist:
- Enable navigation preload so the browser begins the network request while the service worker is still starting.
- Use the preloaded response inside the
fetchevent handler rather than starting a separatefetch()call.
Workbox ships a helper library that feature-detects navigation preload support and wires up the preloaded response for you.



