Skip to content

Network-First Strategy — Fresh Content with Offline Fallback

DodaTech Updated 2026-06-28 6 min read

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

  1. Not setting a timeout. Without a timeout, network-first can hang indefinitely on slow connections. Always set a reasonable timeout (3-8 seconds).
  2. Caching error responses. If the API returns a 500 error, caching it serves broken data offline. Only cache responses with status 200-299.
  3. Caching user-specific data without differentiating URLs. User A's dashboard gets cached and served to User B. Include user IDs in cache keys.
  4. Not providing an offline fallback for navigation. When network-first fails for HTML pages, serve a cached version or offline page.
  5. Caching every API endpoint. Some endpoints should never be cached (auth tokens, CSRF tokens). Whitelist cacheable endpoints.

Practice Questions

  1. When would you choose network-first over cache-first?
  2. Why should you implement a timeout in network-first?
  3. What types of responses should you NOT cache with network-first?
  4. How does exponential backoff improve network-first reliability?
  5. 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

Does network-first work offline on the first visit?

No. If the cache is empty and the network fails, there is no fallback. Pre-cache an offline page during install to handle this case.

How long should the network timeout be?

3-8 seconds is standard. Too short causes false negatives (cached when network works). Too long creates bad UX. Adjust based on your API response times.

Can network-first be combined with push notifications?

Yes. Use network-first to fetch fresh data when the user opens a notification. The cache ensures content is visible even if the network is slow.

How do I clear stale data cached by network-first?

The cache is automatically overwritten on each successful network response. Old entries are replaced naturally. You can also add time-based expiry checks.

Does network-first work for POST requests?

POST requests are not cacheable by design. Do not intercept POST requests. Let the browser handle them normally.

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