Network-First Strategy — Fresh Content with Offline Fallback
In this tutorial, you will learn about Network. We cover key concepts, practical examples, and best practices to help you master this topic.
The network-first Strategy tries the network first and falls back to the cache when offline, ensuring fresh content when connected and graceful degradation when not.
What You'll Learn
By the end of this tutorial, you will understand when to use network-first, how to implement it with timeouts, how to cache responses for offline fallback, and how it compares to other strategies.
Why It Matters
Users expect fresh data — stock prices, messages, news headlines. Network-first delivers the latest content when the user is online while ensuring the app still works during network interruptions. This balance between freshness and reliability is critical for data-driven applications.
Real-World Use
A stock trading PWA uses network-first for real-time prices. When connected, the user sees live data. If the network drops during a commute, the app shows the last cached prices with a "data may be stale" indicator. The user never sees an empty screen or loading spinner stuck forever.
How Network-First Works
Network-First Flow
Request comes in
↓
╔═══════════════════════╗
║ Try network first ║
╚═══════════════════════╝
↓
┌─────────┐ ┌──────────────┐
│ Network │ │ Network │
│ success │ │ fails │
└────┬────┘ └──────┬───────┘
↓ ↓
Cache response Check cache
(for offline) ↓
↓ ┌─────────┐
Return fresh │ Cached │ │ No │
response │ found │ │ cache │
└────┬────┘ └───┬───┘
↓ ↓
Return Return
cached error
response
Think of network-first like calling a friend (network) before checking your notes (cache). If your friend answers, you get the latest information and update your notes. If they do not answer, you check your notes from the last conversation. If you have no notes, you accept that you cannot get the information.
Basic Network-First Implementation
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
fetch(event.request)
.then(networkResponse => {
// Cache the fresh response
if (networkResponse.ok) {
const clone = networkResponse.clone();
caches.open('dynamic-cache').then(cache => {
cache.put(event.request, clone);
console.log('Cached:', event.request.url);
});
}
return networkResponse;
})
.catch(error => {
console.log('Network failed, checking cache:', error.message);
return caches.match(event.request).then(cached => {
if (cached) {
console.log('Serving cached:', event.request.url);
return cached;
}
// No cache either — return offline page
console.log('No cache available for:', event.request.url);
return caches.match('/offline.html');
});
})
);
});
Output:
Cached: https://example.com/api/posts
Network failed, checking cache: TypeError: Failed to fetch
Serving cached: https://api.example.com/data
No cache available for: https://example.com/api/reports
Serving offline page
Network-First with Timeout
Sometimes the network is slow rather than unavailable. A timeout prevents users from waiting indefinitely:
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
const timeout = 3000; // 3 seconds
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Network timeout'));
}, timeout);
});
event.respondWith(
Promise.race([
fetch(event.request),
timeoutPromise
])
.then(networkResponse => {
if (networkResponse.ok) {
const clone = networkResponse.clone();
caches.open('dynamic-cache').then(cache => {
cache.put(event.request, clone);
});
}
return networkResponse;
})
.catch(error => {
console.log(`Network error (${error.message}), using cache`);
return caches.match(event.request).then(cached => {
if (cached) return cached;
return caches.match('/offline.html');
});
})
);
});
Network-First with Exponential Backoff
For critical requests, retry with increasing delays:
async function fetchWithBackoff(request, maxRetries = 3) {
let delay = 1000;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(request);
if (response.ok) return response;
if (attempt === maxRetries) throw new Error('Max retries');
} catch (error) {
if (attempt === maxRetries) throw error;
console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms`);
await new Promise(r => setTimeout(r, delay));
delay *= 2; // Exponential backoff
}
}
}
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
event.respondWith(
fetchWithBackoff(event.request)
.then(response => {
const clone = response.clone();
caches.open('dynamic-cache').then(cache => {
cache.put(event.request, clone);
});
return response;
})
.catch(() => {
return caches.match(event.request).then(cached => {
return cached || caches.match('/offline.html');
});
})
);
});
Network-First with Conditional Caching
Cache only responses that are worth storing offline:
self.addEventListener('fetch', event => {
if (event.request.method !== 'GET') return;
const url = new URL(event.request.url);
// Only cache same-origin API responses
const shouldCache = url.origin === location.origin &&
url.pathname.startsWith('/api/');
event.respondWith(
fetch(event.request)
.then(networkResponse => {
if (shouldCache && networkResponse.ok) {
const clone = networkResponse.clone();
caches.open('api-cache').then(cache => {
// Limit cache to 50 entries
cache.keys().then(keys => {
if (keys.length >= 50) {
cache.delete(keys[0]);
}
cache.put(event.request, clone);
});
});
}
return networkResponse;
})
.catch(() => {
return caches.match(event.request)
.then(cached => cached || caches.match('/offline.html'));
})
);
});
When to Use Network-First
Use network-first for:
- API responses: Data fetched from servers
- User-specific pages: Dashboard, profile, settings
- Real-time data: Notifications, messages, live updates
- Authentication endpoints: Login verification, token refresh
- Content that changes frequently: News feed, product listings
Do NOT use network-first for:
- Static assets: CSS, JS, fonts (use cache-first)
- Large media files: Videos, high-res images
- Third-party scripts: Analytics, ads (network-only)
- Resources that never change: Versioned build files
Performance Tradeoffs
Network-first is slower than cache-first when the network is available but provides fresher content. The tradeoff is measured in milliseconds:
async function compareStrategies(apiUrl) {
// Network-first timing
const nfStart = performance.now();
const networkResponse = await fetch(apiUrl);
const nfTime = performance.now() - nfStart;
// Cache-first timing (after first request cached)
const cfStart = performance.now();
const cachedResponse = await caches.match(apiUrl);
const cfTime = performance.now() - cfStart;
console.log(`Network-first: ${nfTime.toFixed(2)}ms`);
console.log(`Cache-first: ${cfTime.toFixed(2)}ms`);
console.log(`Difference: ${nfTime - cfTime}ms slower for network-first`);
}
// Typical results:
// Network-first: 342.15ms
// Cache-first: 0.83ms
// Difference: 341.32ms slower for network-first
Common Mistakes
- Not setting a timeout. Without a timeout, network-first can hang indefinitely on slow connections. Always set a reasonable timeout (3-8 seconds).
- Caching error responses. If the API returns a 500 error, caching it serves broken data offline. Only cache responses with status 200-299.
- Caching user-specific data without differentiating URLs. User A's dashboard gets cached and served to User B. Include user IDs in cache keys.
- Not providing an offline fallback for navigation. When network-first fails for HTML pages, serve a cached version or offline page.
- Caching every API endpoint. Some endpoints should never be cached (auth tokens, CSRF tokens). Whitelist cacheable endpoints.
Practice Questions
- When would you choose network-first over cache-first?
- Why should you implement a timeout in network-first?
- What types of responses should you NOT cache with network-first?
- How does exponential backoff improve network-first reliability?
- What is the main performance disadvantage of network-first?
Challenge: Implement network-first with a 5-second timeout and exponential backoff (3 retries). Cache only successful same-origin API responses. Log each retry attempt and cache operation. Test by temporarily disabling your server.
FAQ
Mini Project
Create a service worker that uses network-first for all same-origin API calls with a 4-second timeout and 2 retries. Cache only responses with status 200. For navigation requests, use network-first with a fallback to a pre-cached offline page. Test by making API calls, disabling the network, and verifying cached data appears.
What's Next
You have mastered two strategies. Now learn stale-while-revalidate, which combines the speed of cache-first with the freshness of network-first.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro