Service Worker Fetch Event — Intercepting Network Requests
In this tutorial, you will learn about Service Worker Fetch Event. We cover key concepts, practical examples, and best practices to help you master this topic.
The fetch event intercepts every network request from your PWA, allowing you to serve cached responses, fetch from the network, or combine both strategies for optimal performance and offline support.
What You'll Learn
By the end of this tutorial, you will understand how to intercept fetch events, differentiate between request types, implement basic Caching strategies, and handle errors gracefully.
Why It Matters
The fetch event is where your PWA's performance and reliability come to life. Without proper fetch handling, your service worker cannot serve cached content, handle offline scenarios, or optimize network usage. The fetch Strategy you choose directly impacts user experience.
Real-World Use
A weather PWA intercepts fetch requests for the forecast API. It serves cached data instantly while fetching fresh data in the background. Users see yesterday's forecast immediately, which updates to today's within seconds — no loading spinner ever appears.
Fetch Event Basics
Every network request from pages controlled by your service worker triggers a fetch event. You intercept it with event.respondWith() and return a Response object.
Fetch Event Flow
Page makes network request (CSS, JS, API, image)
↓
Service worker receives 'fetch' event
↓
╔═══════════════════════════╗
║ event.respondWith( ║
║ return Response or ║
║ fallback ║
║ ) ║
╚═══════════════════════════╝
↓
Response returned to page
Think of the fetch event like a mail sorter at a post office. Every letter (request) comes to the sorter first. The sorter decides: deliver from local storage (cache), fetch from the sender (network), or send a standard reply (fallback).
Basic Fetch Handler
self.addEventListener('fetch', event => {
console.log('Fetch requested:', event.request.url);
event.respondWith(
caches.match(event.request)
.then(cachedResponse => {
if (cachedResponse) {
console.log('Cache hit:', event.request.url);
return cachedResponse;
}
console.log('Cache miss, fetching:', event.request.url);
return fetch(event.request)
.then(response => {
// Cache the response for next time
const responseClone = response.clone();
caches.open('dynamic-cache').then(cache => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(error => {
console.log('Network failed:', error.message);
return caches.match('/offline.html');
});
})
);
});
Output:
Fetch requested: https://example.com/styles/main.css
Cache hit: https://example.com/styles/main.css
Fetch requested: https://example.com/api/data
Cache miss, fetching: https://example.com/api/data
Network failed: TypeError: Failed to fetch
Served offline fallback
Differentiating Request Types
Different resource types need different strategies. Check the request destination or URL:
self.addEventListener('fetch', event => {
const { request } = event;
const url = new URL(request.url);
// Skip non-GET requests
if (request.method !== 'GET') return;
// Same-origin requests
if (url.origin === location.origin) {
if (request.destination === 'style') {
// Cache-first for CSS
event.respondWith(cacheFirst(request));
} else if (request.destination === 'script') {
// Cache-first for JS
event.respondWith(cacheFirst(request));
} else if (request.destination === 'image') {
// Cache-first for images
event.respondWith(cacheFirst(request));
} else if (url.pathname.startsWith('/api/')) {
// Network-first for API calls
event.respondWith(networkFirst(request));
} else {
// Network-first for navigation
event.respondWith(networkFirst(request));
}
} else {
// Cross-origin: network-only with timeout
event.respondWith(networkWithTimeout(request, 3000));
}
});
function cacheFirst(request) {
return caches.match(request).then(cached => {
return cached || fetch(request);
});
}
function networkFirst(request) {
return fetch(request).catch(() => {
return caches.match(request);
});
}
function networkWithTimeout(request, timeout) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), timeout);
});
return Promise.race([
fetch(request),
timeoutPromise
]).catch(() => caches.match(request));
}
Handling Different HTTP Methods
Only GET requests are cacheable. For POST, PUT, DELETE requests, always go to the network:
self.addEventListener('fetch', event => {
const { request } = event;
// Only handle GET requests
if (request.method !== 'GET') {
return; // Let the browser handle normally
}
event.respondWith(
caches.match(request)
.then(cached => cached || fetch(request))
.catch(() => caches.match('/offline.html'))
);
});
Responding with Custom Responses
You can create synthetic responses for offline scenarios:
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// Generate an inline SVG placeholder for missing images
if (event.request.destination === 'image') {
event.respondWith(
caches.match(event.request)
.then(cached => {
if (cached) return cached;
// Return a simple placeholder SVG
return new Response(
`<svg xmlns="http://www.w3.org/2000/svg"
width="400" height="300"
viewBox="0 0 400 300">
<rect fill="#eee" width="400" height="300"/>
<text fill="#999" font-size="20"
x="50%" y="50%" text-anchor="middle"
dominant-baseline="middle">
Image Offline
</text>
</svg>`,
{ headers: { 'Content-Type': 'image/svg+xml' } }
);
})
);
}
});
Logging and Analytics
Track which requests hit cache versus network:
self.addEventListener('fetch', event => {
const startTime = performance.now();
event.respondWith(
caches.match(event.request)
.then(cached => {
if (cached) {
logMetric('cache-hit', event.request.url, performance.now() - startTime);
return cached;
}
return fetch(event.request).then(response => {
logMetric('cache-miss', event.request.url, performance.now() - startTime);
return response;
});
})
);
});
function logMetric(type, url, duration) {
console.log(`[${type}] ${url} (${duration.toFixed(0)}ms)`);
// You could also send this to an analytics endpoint
// But be careful not to create infinite loops
// Use a separate cache or Background Sync for analytics
}
Output:
[cache-hit] https://example.com/styles/main.css (2ms)
[cache-miss] https://example.com/api/data (340ms)
[cache-hit] https://example.com/logo.png (1ms)
Common Mistakes
- Not returning early for non-GET requests. POST requests should not be intercepted. Always check
request.method !== 'GET'and return early. - Making fetch handlers too slow. Complex sync operations in the fetch handler block the response. Keep fetch handlers lean and use caches.match() as your primary operation.
- Caching opaque responses without checking. Cross-origin fetch responses may be opaque (status 0). Do not cache them blindly. Verify the response is valid.
- Modifying request headers in the service worker. Some headers cannot be modified. Use caution when creating new Request objects.
- Forgetting to clone the response. A response body can only be consumed once. Clone before caching if you also return the original.
Practice Questions
- What does event.respondWith() do in the fetch handler?
- Why should you skip non-GET requests in the fetch event?
- What is the difference between request.destination and URL path matching?
- Why must you clone a response before caching it?
- How would you implement a timeout for network requests in the fetch handler?
Challenge: Write a fetch handler that applies three different strategies: cache-first for CSS/JS/images, network-first for API calls, and a 5-second timeout for cross-origin resources. Log each decision.
FAQ
Mini Project
Create a fetch handler that applies three strategies based on request type: cache-first for static assets, network-first for API calls, and a fallback to an offline page for navigation requests. Test by registering the service worker, checking DevTools network tab for cache hits, and verifying offline behavior.
What's Next
You now know how to intercept requests. Learn about the Cache Storage API to understand how the cache works under the hood.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro