The service worker fetch event and routing
In one line: Every network request made by a page in the service worker’s scope fires a fetch event in the worker. event.respondWith(promise) lets the worker intercept the request and return any Response — from cache, network, or constructed inline.
The FetchEvent interface
Section titled “The FetchEvent interface”A FetchEvent carries:
event.request— theRequestobject, includingurl,method,headers,destination, andmode.event.respondWith(responsePromise)— call this to take control of the response. If you don’t call it, the request falls through to the network as normal.event.waitUntil(promise)— extend the worker’s lifetime past the event, for side effects like cache warming.event.clientId— the identifier of the client (tab) that made the request.event.resultingClientId— the new client ID if a navigation request creates a new client.
Basic interception
Section titled “Basic interception”self.addEventListener('fetch', (event) => { // Only intercept requests to our own origin if (!event.request.url.startsWith(self.location.origin)) return;
event.respondWith( caches.match(event.request).then((cached) => cached ?? fetch(event.request)) );});Returning without calling respondWith() passes the request through to the browser’s normal network stack.
Request properties for routing decisions
Section titled “Request properties for routing decisions”| Property | Values and meaning |
|---|---|
request.destination |
'document', 'script', 'image', 'font', 'fetch', '' (XHR/fetch with no type) |
request.mode |
'navigate' (page navigation), 'cors', 'no-cors', 'same-origin' |
request.method |
'GET', 'POST', 'PUT', etc. |
request.url |
Full URL string; use new URL(event.request.url) for pathname matching |
Routing patterns
Section titled “Routing patterns”By destination
Section titled “By destination”self.addEventListener('fetch', (event) => { const { destination } = event.request;
if (destination === 'image') { event.respondWith(cacheFirst(event.request, 'images')); } else if (destination === 'document') { event.respondWith(networkFirst(event.request, 'pages')); } // other requests fall through});By URL prefix
Section titled “By URL prefix”self.addEventListener('fetch', (event) => { const url = new URL(event.request.url);
if (url.pathname.startsWith('/api/')) { event.respondWith(networkOnly(event.request)); } else if (url.pathname.startsWith('/static/')) { event.respondWith(cacheFirst(event.request, 'static')); }});Navigation requests (for SPA offline support)
Section titled “Navigation requests (for SPA offline support)”For single-page apps, all navigate requests should return the cached app shell:
self.addEventListener('fetch', (event) => { if (event.request.mode === 'navigate') { event.respondWith( caches.match('/index.html').then((cached) => cached ?? fetch(event.request)) ); return; } // handle other request types…});Constructing a Response inline
Section titled “Constructing a Response inline”respondWith() accepts any Response, including one you construct:
event.respondWith( new Response('<h1>Offline</h1>', { headers: { 'Content-Type': 'text/html' }, }));This is the basis for offline fallback pages. See Offline fallback.
What respondWith does not cover
Section titled “What respondWith does not cover”- Non-interceptable requests. Requests with
mode: 'no-cors'from<link rel="preload">or third-party iframes may not fire afetchevent depending on context. - Opaque responses. Cross-origin requests fetched with
no-corsproduce opaque responses (status: 0). You can cache and return them, but cannot read their body or status. - POST and mutation requests. The fetch event fires for POST too, but caching mutation responses requires careful design (see Background Sync for deferring mutations offline).
Using Workbox for routing
Section titled “Using Workbox for routing”Workbox’s registerRoute is a cleaner way to express routing rules than a long if/else chain in the fetch listener:
import { registerRoute } from 'workbox-routing';import { CacheFirst, NetworkFirst } from 'workbox-strategies';
registerRoute(({ request }) => request.destination === 'image', new CacheFirst());registerRoute(({ request }) => request.mode === 'navigate', new NetworkFirst());See Workbox for full details.
Practical checklist
Section titled “Practical checklist”- Only call
event.respondWith()when you intend to handle the request; let everything else fall through. - Check
event.request.methodbefore caching — never cache non-GET responses unless you specifically need to. - Use
new URL(event.request.url).originto distinguish same-origin from cross-origin requests before routing. - Handle the case where both cache and network fail — return a fallback
Responseto avoid a blank error page. - Test routing logic with DevTools Network throttling set to “Offline”.
Cross-references
Section titled “Cross-references”- Caching strategies — the strategies invoked by
respondWith() - The Cache API —
caches.match()and related methods used inside handlers - Offline fallback — constructing a meaningful response when everything fails
- Workbox — production-ready routing and strategy library