Skip to main content

Offline-First

Offline-first is an application design approach where the app works without a network connection, gracefully syncing when connectivity returns. Unlike local-first where data lives primarily on the device, offline-first treats the cloud as the source of truth but ensures the app remains functional during network outages.

Core Principles

  1. Assume no network: Design for offline as the default state
  2. Cache aggressively: Store data locally for offline access
  3. Sync gracefully: Handle reconnection without user intervention
  4. Show stale data: Better than showing nothing or errors
  5. Queue operations: Store actions for later execution

Offline-First vs Local-First

AspectOffline-FirstLocal-First
Primary storageCloudDevice
Offline behaviourCached copyFull app
Sync directionServer → DeviceBidirectional
Without internetDegradedFull function

Architecture

┌─────────────────────────────────────────┐
│ Application │
│ ┌─────────────────────────────────┐ │
│ │ Service Worker │ │
│ │ • Cache API responses │ │
│ │ • Queue failed requests │ │
│ │ • Background sync │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Local Cache │ │
│ │ (IndexedDB, Cache API) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘

┌───────────────┐
│ Cloud API │ (when available)
│ (Source of │
│ Truth) │
└───────────────┘

Implementation Strategies

Cache-First Pattern

// Service Worker
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cached) => {
// Return cached if available, fetch in background
const fetched = fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('v1').then((cache) => cache.put(event.request, clone));
return response;
});
return cached || fetched;
})
);
});

Optimistic Updates with Queue

async function createItem(item) {
// Update UI immediately
localDb.add(item);
updateUI();

// Queue for sync
try {
await api.create(item);
} catch (error) {
// Add to retry queue
syncQueue.add({ action: 'create', data: item });
}
}

Key Technologies

TechnologyPurpose
Service WorkersIntercept network requests
IndexedDBStructured data storage
Cache APIHTTP response caching
Background Sync APIQueue operations for reconnect
PWAFull offline web apps

What We Like

  • Reliability: App works regardless of network quality
  • Performance: Local reads are instant
  • User experience: No loading spinners for cached data
  • Mobile-friendly: Essential for spotty connections

What We Don't Like

  • Complexity: Sync logic and conflict handling
  • Stale data: Users might see outdated information
  • Cache invalidation: Knowing when to refresh
  • Testing: Simulating offline scenarios is tricky

Common Use Cases

  • Email clients (read offline, queue sends)
  • News and content apps
  • Field service applications
  • Travel apps (maps, reservations)
  • Collaborative tools

Best Practices

  1. Indicate online/offline status: Let users know the connection state
  2. Show data freshness: "Last updated 5 minutes ago"
  3. Handle conflicts: Decide how to merge divergent data
  4. Test offline thoroughly: Simulate various network conditions
  5. Progressive enhancement: Core features work offline, extras need network