Offline Functionality Issues | Blue Frog Docs

Offline Functionality Issues

Diagnose and fix PWA offline support and cache strategies

Offline Functionality Issues

What This Means

Offline functionality allows users to continue using your app without network connectivity. Issues include:

  • App failing completely when offline
  • Stale content being displayed
  • Missing offline fallback pages
  • Incomplete asset caching

How to Diagnose

1. Test Offline Mode

  1. DevTools > Network tab
  2. Check Offline checkbox
  3. Reload the page
  4. Observe behavior

2. Check Cached Assets

DevTools > Application > Cache Storage:

  • List all cached items
  • Verify critical assets are cached

3. Service Worker Fetch Handler

// Console: Check if SW handles fetch
navigator.serviceWorker.controller
// If null, no active SW controlling the page

General Fixes

Offline Page Setup

<!-- offline.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Offline - BlueFrog Analytics</title>
  <style>
    body {
      font-family: system-ui, sans-serif;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      margin: 0;
      background: #f5f5f5;
    }
    .container { text-align: center; padding: 2rem; }
    h1 { color: #333; }
    p { color: #666; }
    button {
      background: #1f75fe;
      color: white;
      border: none;
      padding: 12px 24px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 16px;
      margin-top: 1rem;
    }
    button:hover { background: #1565d8; }
  </style>
</head>
<body>
  <div class="container">
    <h1>You're Offline</h1>
    <p>Please check your internet connection and try again.</p>
    <button onclick="window.location.reload()">Retry</button>
  </div>
  <script>
    // Auto-reload when back online
    window.addEventListener('online', () => window.location.reload());
  </script>
</body>
</html>

Cache Offline Page in SW

// sw.js
const CACHE_NAME = 'offline-v1';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache => {
      return cache.addAll([
        OFFLINE_URL,
        '/styles/offline.css',
        '/images/offline-icon.svg'
      ]);
    })
  );
  self.skipWaiting();
});

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(
      fetch(event.request).catch(() => {
        return caches.match(OFFLINE_URL);
      })
    );
  }
});

App Shell Architecture

Cache the app shell for instant offline loads:

// sw.js
const APP_SHELL = [
  '/',
  '/index.html',
  '/styles/main.css',
  '/scripts/app.js',
  '/images/logo.svg',
  '/fonts/main.woff2',
  '/offline.html'
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('app-shell-v1').then(cache => {
      return cache.addAll(APP_SHELL);
    })
  );
});

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);

  // App shell: cache-first
  if (APP_SHELL.includes(url.pathname)) {
    event.respondWith(
      caches.match(event.request).then(cached => {
        return cached || fetch(event.request);
      })
    );
    return;
  }

  // Other requests: network-first with offline fallback
  event.respondWith(
    fetch(event.request)
      .catch(() => caches.match(event.request))
      .catch(() => caches.match('/offline.html'))
  );
});

Workbox Offline Recipe

// Using Workbox for robust offline support
import { precacheAndRoute } from 'workbox-precaching';
import { offlineFallback } from 'workbox-recipes';
import { setDefaultHandler } from 'workbox-routing';
import { NetworkOnly } from 'workbox-strategies';

// Precache static assets
precacheAndRoute(self.__WB_MANIFEST);

// Set default to network only
setDefaultHandler(new NetworkOnly());

// Enable offline fallback
offlineFallback({
  pageFallback: '/offline.html',
  imageFallback: '/images/offline-placeholder.png',
  fontFallback: null
});

Background Sync for Forms

// sw.js - Queue form submissions for offline
self.addEventListener('sync', (event) => {
  if (event.tag === 'form-sync') {
    event.waitUntil(syncForms());
  }
});

async function syncForms() {
  const db = await openDB('forms', 1);
  const pending = await db.getAll('pending-submissions');

  for (const form of pending) {
    try {
      await fetch('/api/submit', {
        method: 'POST',
        body: JSON.stringify(form.data)
      });
      await db.delete('pending-submissions', form.id);
    } catch (error) {
      console.error('Sync failed:', error);
    }
  }
}

// main.js - Register for background sync
async function submitForm(data) {
  if (navigator.onLine) {
    return fetch('/api/submit', { method: 'POST', body: data });
  }

  // Save for later sync
  const db = await openDB('forms', 1);
  await db.add('pending-submissions', { data, timestamp: Date.now() });

  // Request background sync
  const registration = await navigator.serviceWorker.ready;
  await registration.sync.register('form-sync');
}

Platform-Specific Guides

Platform Guide
Next.js Next.js Offline
Gatsby Gatsby Offline

Verification

  1. Toggle DevTools Network to "Offline"
  2. Navigate around the app
  3. Verify cached pages load
  4. Check offline page appears for uncached routes
  5. Return online and verify sync works

Common Mistakes

Mistake Fix
Not caching offline page in install Add to precache list
No fetch handler for navigation Handle mode: 'navigate' requests
Caching HTML without versioning Use cache versioning
Not testing on real devices Test offline on mobile devices

Further Reading

// SYS.FOOTER