Developer Guide
Warning: JavaScript ahead! This is a technical teardown meant for the people writing your code. If you're focusing on high-level strategy, feel free to forward this directly to your dev team.

Your website probably runs beautifully in your office, on fast Wi-Fi and a new laptop. But most of your users aren't in that environment. They're on trains, in elevators, in remote towns, switching between Wi-Fi and mobile data - or just dealing with dodgy signal on their phones.

For them, the internet isn't a steady stream - it's a lightbulb that flickers.

And when the connection drops, does your website fall apart? Or does it adapt?

If your answer is “it breaks,” you're not just losing functionality - you're losing customers, trust, and opportunities.

Test This Live

We practice what we preach. Because Pathfinder Digital implements these strategies, you can test this concept right now.

Turn off your Wi-Fi or put your device into Airplane Mode.

Then, refresh this page. The article will still load instantly, even though you are offline. That is the reality of a resilient, defensively-built website.

Why Dropouts Matter

  • Trains go through tunnels or lose signal between towers
  • Wi-Fi cuts out in cafes and hotels
  • People switch between networks while moving around
  • Rural and regional users often have unreliable infrastructure
  • Mobile data gets throttled, capped, or disabled mid-session

These aren't "what if" situations. They're normal user behaviour.

And when your website doesn't handle those dropouts well, users don't blame the network - they blame your brand.

The Cost of Dropouts

  • Form submissions fail, and users lose their data
  • API requests break, leaving empty screens or endless spinners
  • Images and content don't load, making your site unusable
  • No feedback is given, so the user thinks the site is broken

And here's the worst part: you might not even see these failures in your analytics. Dropouts leave silent gaps - sessions that vanish without explanation.

Building for Dropouts

Normal Website During a Dropout User Connection Lost Server Resilient Website During a Dropout User Instant Load Service Worker Connection Lost Server Local Cache

Strategic Caching

Service workers let your site respond even when the network disappears. They can intercept requests and serve pre-cached versions of your pages, styles, scripts, and images.

However, treating all requests equally is a mistake. If you blindly cache your HTML documents, users might see stale content and have to double-refresh to see newly published articles. The most robust approach is a Split Caching Strategy:

  • For HTML Documents: Use a Network-First pattern. The service worker tries to fetch the absolute freshest page from the server. If the fetch fails (or hangs for too long), it instantly falls back to serving the cached version.
  • For Static Assets (CSS, JS, Fonts): Use a Stale-While-Revalidate pattern. The service worker instantly delivers the cached asset so the page feels lightning fast, while quietly updating the cache in the background.

The user gets the best of both worlds: guaranteed freshness for content, and instant loading for heavy assets. Here is a basic implementation of a split strategy in your sw.js file:


self.addEventListener('fetch', (event) => {
    if (event.request.method !== 'GET') return;

    // Detect if the user is requesting an HTML document
    const isHtml = event.request.mode === 'navigate' || 
                   event.request.destination === 'document';

    event.respondWith(
        caches.open('my-site-cache').then(async (cache) => {
            const cachedResponse = await cache.match(event.request);
            
            // The promise to fetch fresh data and update the cache
            const networkFetch = fetch(event.request).then((netResp) => {
                cache.put(event.request, netResp.clone());
                return netResp;
            }).catch(() => null);

            if (isHtml) {
                // HTML Strategy: Network-First (Fallback to Cache)
                const netResp = await networkFetch;
                if (netResp) return netResp;           // Return fresh HTML
                if (cachedResponse) return cachedResponse; // Offline fallback
                return caches.match('/offline.html');
            } else {
                // Asset Strategy: Stale-While-Revalidate
                if (cachedResponse) return cachedResponse; // Return instant cache
                return networkFetch;                       // Fallback to network
            }
        })
    );
});
  

Beware of "Lie-Fi"

The most frustrating scenario isn't when the internet drops completely - it's Lie-Fi. This is when a phone shows 4G/5G bars, but absolutely zero data is moving. In this state, a standard network request will hang indefinitely until the browser finally times out (which can take 60+ seconds).

Every fetch request should be written with the assumption that it might fail or hang. If a request to an API breaks due to a lost signal, your user shouldn't be stuck with a broken page or an endless loading spinner.

  • Enforce strict network timeouts (e.g., abort requests that take longer than 4 seconds) to quickly trigger fallbacks
  • Catch network errors with try/catch blocks
  • Show fallback UI (e.g. last-known good content, or a helpful message)
  • Disable certain buttons or actions until connectivity is restored
  • Offer the user a retry button or automatic reconnection option

Always give users feedback, not frustration.

Exponential Backoff

When something fails - like submitting a form - your site should try again. But not immediately and repeatedly. That's how you overload servers and drain mobile batteries.

Use exponential backoff:

  • First retry after 1 second
  • Then 2 seconds
  • Then 4, 8, etc.

You can even store actions in localStorage or IndexedDB and resend them when the user comes back online.


    function retryRequest(url, options, attempts = 5, delay = 1000) {
      return fetch(url, options).catch(err => {
        if (attempts === 0) throw err;
        return new Promise(resolve => 
          setTimeout(() => resolve(retryRequest(url, options, attempts - 1, delay * 2)), delay)
        );
      });
    }
  

This strategy lets your site bend instead of break when something goes wrong.

Communicate Offline Status

Silence during a dropout is the worst thing you can do. If your app stops responding and gives no hint why, the user will assume something's broken - and leave.

  • Show a persistent “You're offline” banner or warning
  • Disable or grey-out actions that require a connection
  • Re-enable those actions automatically when the connection returns
  • Offer manual "Retry" or "Refresh" options

Queue and Sync Later

If your user does something meaningful while offline - submits a form, hits “like,” adds something to a cart - don't throw that action away.

  • Save the action to localStorage or IndexedDB
  • Monitor the connection status
  • Automatically resend those actions when the user is back online

Pro Tip: The Background Sync API
Modern Service Workers can be handed a queued action (like a "Contact Us" form) and the browser will automatically send it the moment the phone regains signal - even if the user has completely closed your website.

To the user, it feels like everything just worked - even though your site was smartly waiting for the right moment behind the scenes.

Detect Connectivity Changes

Your website can detect when a user goes offline - or comes back online - and respond accordingly.

Use navigator.onLine to check status:


    if (!navigator.onLine) {
      showOfflineBanner();
    }
  

Listen for connectivity changes:


    window.addEventListener('offline', () => {
      showOfflineBanner();
    });

    window.addEventListener('online', () => {
      hideOfflineBanner();
      resendQueuedActions();
    });
  

Bonus: Use navigator.connection to check connection quality


    if ('connection' in navigator) {
      console.log(navigator.connection.effectiveType);
    }
  

Track Connection Status in Your Analytics


    function trackConnectionStatus() {
      const status = navigator.onLine ? 'online' : 'offline';
      sendToAnalytics('connection_status', status);
    }

    window.addEventListener('online', trackConnectionStatus);
    window.addEventListener('offline', trackConnectionStatus);
  

This data helps you build with insight, not guesswork.

Test Realistically

  • Use Chrome DevTools → Network → Offline / Slow 3G
  • CPU throttling to mimic older devices
  • Use WebPageTest, Lighthouse, or Replay.io to simulate dropouts
  • Use RUM tools like Google Analytics 4, Sentry, or LogRocket

Test things like:

  • Reloading the page while offline
  • Submitting a form with no connection
  • Navigating back and forth under flaky network conditions

The Opaque Cache Trap

When you set up aggressive caching, it's easy to accidentally cache everything - including third-party scripts like Google Tag Manager, analytics pixels, or external CDNs. But doing this blindly introduces a massive performance penalty known as the Opaque Response trap.

For security reasons (specifically to prevent cross-origin data leaks), browsers obscure the details of cross-origin requests that don't use CORS. When your Service Worker caches an "opaque" response, the browser artificially pads its size. A 1KB tracking pixel might be padded to consume 7 Megabytes of storage quota!

If your site caches 15 external trackers, you might unintentionally eat up 100MB of a user's mobile storage, causing the browser to quickly purge your cache.

The Fix: Always check the response type before caching. Explicitly reject opaque responses and only cache first-party assets or external resources configured with CORS (like Google Fonts).


    // Inside your Service Worker's fetch event:
    if (response && response.ok && response.type !== 'opaque') {
       cache.put(request, response.clone());
    }
  

Look Under The Hood

If you're a developer and want to see how these concepts come together in production, you are welcome to inspect our own Service Worker.

Our sw.js script goes far beyond basic caching. It includes bespoke logic to:

  • Actively fetch and parse our sitemap.php XML to automatically precache all valid URLs in the background.
  • Intercept offline form submissions, queue them, and fire them off seamlessly via the Background Sync API once the connection is restored.
  • Trigger automated Slack notifications upon successful background syncs.
  • Power our AI-driven 404 handler fallback system.

You can view the raw source code right here or by opening Chrome DevTools and navigating to the Application tab.

Conclusion

Connection dropouts are not rare. They're a routine part of your users' day-to-day experience...

When a dropout happens, does your site power through - or fall to pieces?

Resilient sites:

  • Cache smartly, so users always see something useful
  • Fail gracefully, with feedback and fallbacks
  • Retry intelligently, with exponential backoff and queued actions
  • Sync later, without forcing the user to redo anything
  • Detect and adapt, showing helpful messaging when the signal drops
  • Track everything, so you're not flying blind

So don't just build fast websites. Build forgiving, adaptable, dropout-resistant websites. Because when the connection cuts out, the real test begins - and most sites fail it.

Want help building a site that doesn't give up when the network does?

Contact us. We'll help you make your website more resilient, more reliable, and ready for the real world.

Contact Us