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
- DevTools > Network tab
- Check Offline checkbox
- Reload the page
- 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
- Toggle DevTools Network to "Offline"
- Navigate around the app
- Verify cached pages load
- Check offline page appears for uncached routes
- 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 |