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
- Assume no network: Design for offline as the default state
- Cache aggressively: Store data locally for offline access
- Sync gracefully: Handle reconnection without user intervention
- Show stale data: Better than showing nothing or errors
- Queue operations: Store actions for later execution
Offline-First vs Local-First
| Aspect | Offline-First | Local-First |
|---|---|---|
| Primary storage | Cloud | Device |
| Offline behaviour | Cached copy | Full app |
| Sync direction | Server → Device | Bidirectional |
| Without internet | Degraded | Full 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
| Technology | Purpose |
|---|---|
| Service Workers | Intercept network requests |
| IndexedDB | Structured data storage |
| Cache API | HTTP response caching |
| Background Sync API | Queue operations for reconnect |
| PWA | Full 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
- Indicate online/offline status: Let users know the connection state
- Show data freshness: "Last updated 5 minutes ago"
- Handle conflicts: Decide how to merge divergent data
- Test offline thoroughly: Simulate various network conditions
- Progressive enhancement: Core features work offline, extras need network
Related Concepts
- Local-first: Device-primary storage
- PWA: Progressive Web Apps
- IndexedDB: Client-side database
- Service Workers: Background network handling