Service Worker Navigation Preload Explained
Navigation preload is a performance optimization feature that eliminates the delay caused when a service worker must boot up before fetching critical network resources. By allowing the browser to download navigation requests in parallel with the service worker’s activation, developers can avoid blank screens and significantly reduce Time to First Byte (TTFB). This article covers the mechanics behind this startup delay, how navigation preload resolves it, and how to implement it directly in your web application.
The Service Worker Startup Latency Problem
When a user navigates to a web page controlled by a standard service
worker, the browser must first launch the service worker execution
thread if it is currently inactive (idle). In a default setup, the
browser intercepts the navigation request, spins up the worker, waits
for the worker to initialize, and only then triggers the
fetch event handler.
This creates a serial bottleneck:
- Navigation begins.
- The browser launches the service worker thread (which can take 50ms to 500ms depending on device capabilities).
- The
fetchlistener executes. - The service worker finally initiates
fetch(event.request)to the network.
If the requested resource is not cached and must be retrieved from the network, the user suffers an unnecessary delay while waiting for the worker to start just to issue the same request.
How Navigation Preload Solves the Delay
Navigation preload changes this execution model from serial to parallel. When enabled, as soon as a user initiates a navigation request, the browser immediately does two things at the exact same time:
- Sends the network request for the requested page directly to the server.
- Boots up the service worker thread in the background.
By the time the service worker finishes launching and triggers its
fetch event listener, the network response may already be
partially or fully downloaded. Instead of making a brand-new network
request, the service worker simply consumes the preloaded response via a
dedicated Promise.
Implementing Navigation Preload
To use navigation preload, you must enable it during the service
worker's activate phase and consume the resulting response
inside the fetch handler.
Step 1: Enable Preload on Activation
You check for feature support and enable the capability using the
navigationPreload API on the service worker registration
object:
self.addEventListener('activate', (event) => {
event.waitUntil(
(async () => {
if (self.registration.navigationPreload) {
await self.registration.navigationPreload.enable();
}
})()
);
});Step 2: Consume the Response in the Fetch Event
When a navigation occurs, access event.preloadResponse
inside the fetch listener. If a preloaded response is
available, return it; otherwise, fall back to the cache or execute a
standard fetch call:
self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
(async () => {
try {
// Check if the preloaded response is ready
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
// Fallback to standard network request if preload was empty
return await fetch(event.request);
} catch (error) {
// Fallback to an offline page or cache if network fails
const cache = await caches.open('offline-fallback');
return await cache.match('/offline.html');
}
})()
);
}
});Server Identification with Headers
When navigation preload sends a request, the browser automatically attaches a custom HTTP header to the request:
Service-Worker-Navigation-Preload: true
You can also customize the value of this header via
registration.navigationPreload.setHeaderValue(value). This
allows backend servers to detect that the request is a preload request.
For advanced architectures, servers can respond with a lighter version
of the HTML, stream specific fragments, or initiate server-push logic
tailored for service worker caching strategies.