local-first-fundamentals
Teaches the local-first software paradigm where applications store data on the user's device, work fully offline, and sync to peers or servers when connectivity is available. Covers the spectrum from cloud-first to offline-first to local-first, core benefits (instant UX, offline capability, data ownership, privacy), key challenges (conflict resolution, sync complexity, storage limits), architectural patterns, and decision frameworks for when local-first is the right choice.
Build software that works offline, keeps data on the user's device, and syncs when connected. ## Key Points - Data lives on the server - App is a thin client rendering server state - Offline = broken - Examples: traditional SaaS, Google Docs (pre-offline), most web apps - Data cached locally for offline use - Server remains the authority - Conflicts resolved server-side (last-write-wins or manual) - Examples: Progressive Web Apps with service workers, mobile apps with local caches - Data originates and lives on the device - Every device holds a full (or partial) replica - Sync is peer-to-peer or via relay servers - Conflicts resolved automatically via CRDTs or similar ## Quick Example ``` Cloud-First: Device --> Server (authority) --> Device Offline-First: Device --> Cache locally --> Server when available Local-First: Device (authority) <--> Sync <--> Device (authority) ```
skilldb get local-first-skills/local-first-fundamentalsFull skill: 285 linesLocal-First Fundamentals
Build software that works offline, keeps data on the user's device, and syncs when connected.
What Local-First Means
Local-first software stores the primary copy of data on the user's device. The server is a peer, not the authority. The app works without a network connection, and syncing happens in the background when connectivity exists.
Key principle: the user's device is the source of truth, not a remote server.
Cloud-First: Device --> Server (authority) --> Device
Offline-First: Device --> Cache locally --> Server when available
Local-First: Device (authority) <--> Sync <--> Device (authority)
Cloud-First
- Data lives on the server
- App is a thin client rendering server state
- Offline = broken
- Examples: traditional SaaS, Google Docs (pre-offline), most web apps
Offline-First
- Data cached locally for offline use
- Server remains the authority
- Conflicts resolved server-side (last-write-wins or manual)
- Examples: Progressive Web Apps with service workers, mobile apps with local caches
Local-First
- Data originates and lives on the device
- Every device holds a full (or partial) replica
- Sync is peer-to-peer or via relay servers
- Conflicts resolved automatically via CRDTs or similar
- Examples: Figma (hybrid), Linear (hybrid), Obsidian Sync, Anytype
Core Benefits
1. Instant User Experience
No loading spinners. No waiting for server round-trips. Every read and write hits local storage.
// Cloud-first: every action waits for the server
async function addTodo(text) {
const response = await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ text }),
});
// UI updates after server responds (200-500ms)
return response.json();
}
// Local-first: write locally, sync in background
function addTodo(text) {
// UI updates instantly (< 1ms)
db.todos.add({ id: generateId(), text, done: false });
// Sync happens automatically in the background
}
2. Offline Capability
The app works without any network connection. Users on planes, in subways, or in areas with poor connectivity have full functionality.
3. Data Ownership and Privacy
Data stays on the user's device. No vendor lock-in. Users can export, back up, and control their data. Sensitive information never has to touch a third-party server.
4. Resilience
If the server goes down, the app keeps working. If the company shuts down, the user still has their data. No single point of failure.
5. Collaboration Without Central Servers
Peer-to-peer sync allows real-time collaboration without routing through a central server. Devices on the same LAN can sync directly.
Core Challenges
1. Conflict Resolution
When two devices modify the same data offline, conflicts arise on sync.
Device A (offline): rename file to "report-v2.pdf"
Device B (offline): rename file to "final-report.pdf"
|
Both come online
|
Which name wins?
Strategies:
- Last-Write-Wins (LWW): Simple but lossy. One edit is silently discarded.
- CRDTs: Mathematically guarantee convergence without data loss. More complex.
- Operational Transform (OT): Transforms operations relative to each other. Used by Google Docs. Requires a central server.
- Manual resolution: Show conflicts to the user. Git-style.
2. Sync Complexity
Building a reliable sync engine is hard:
- Partial sync (not all data on all devices)
- Bandwidth optimization (don't re-send everything)
- Ordering guarantees (causal consistency)
- Schema evolution (what if devices run different app versions?)
3. Storage Limits
Browsers limit IndexedDB storage (varies by browser, typically 50% of disk up to a cap). Mobile devices have tighter constraints. Large datasets need strategies like pagination, eviction, and selective sync.
4. Security Model
When data lives on the device, encryption and access control must work locally:
- End-to-end encryption for synced data
- Local key management
- Device authorization and revocation
Architectural Patterns
Pattern 1: CRDT-Based Document Sync
Best for: collaborative editing, shared workspaces.
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Device A │ │ Relay │ │ Device B │
│ │ │ Server │ │ │
│ ┌──────┐ │ │ │ │ ┌──────┐ │
│ │ CRDT │◄├─────┤► Merge ◄├─────┤►│ CRDT │ │
│ │ Doc │ │ │ │ │ │ Doc │ │
│ └──────┘ │ │ │ │ └──────┘ │
│ ┌──────┐ │ └───────────┘ │ ┌──────┐ │
│ │ IDB │ │ │ │ IDB │ │
│ └──────┘ │ │ └──────┘ │
└──────────┘ └──────────┘
Libraries: Yjs, Automerge, Loro
Pattern 2: Optimistic Mutations with Server Reconciliation
Best for: apps that need server-side validation and authorization.
┌──────────┐ ┌──────────┐
│ Client │ │ Server │
│ │ │ │
│ 1. Mutate├────────►│ 3. Apply │
│ locally│ │ or │
│ 2. Queue │ │ reject │
│ mutation│ │ 4. Push │
│ │◄────────┤ canonical│
│ 5. Rebase│ │ state │
│ local │ │ │
└──────────┘ └──────────┘
Libraries: Zero (Rocicorp), Replicache, PowerSync
Pattern 3: Event Sourcing with Local Log
Best for: apps where full history matters (finance, audit trails).
// Every change is an immutable event
const events = [
{ type: 'ITEM_ADDED', id: 'a1', name: 'Milk', ts: 1700000000 },
{ type: 'ITEM_CHECKED', id: 'a1', ts: 1700000060 },
{ type: 'ITEM_ADDED', id: 'a2', name: 'Bread', ts: 1700000120 },
];
// Current state is derived by replaying events
function deriveState(events) {
return events.reduce((state, event) => {
switch (event.type) {
case 'ITEM_ADDED':
return { ...state, [event.id]: { name: event.name, checked: false } };
case 'ITEM_CHECKED':
return { ...state, [event.id]: { ...state[event.id], checked: true } };
default:
return state;
}
}, {});
}
When to Use Local-First
Strong Fit
- Collaborative editing: documents, whiteboards, design tools
- Note-taking and personal knowledge management: Obsidian-like apps
- Field work applications: data collection where connectivity is unreliable
- Privacy-sensitive domains: health data, financial records, journaling
- Real-time multiplayer: games, shared cursors, live collaboration
- Developer tools: code editors, local databases, CLI tools
Weak Fit
- Server-authoritative systems: banking transactions, inventory with strict consistency
- Large dataset processing: analytics dashboards querying terabytes of data
- Ephemeral content: feeds, timelines where data is consumed and discarded
- Regulatory requirements: when data must live in specific server jurisdictions
Decision Checklist
[ ] Users need the app to work offline
[ ] Latency matters (instant responses expected)
[ ] Users want to own/control their data
[ ] Collaboration between devices or users is needed
[ ] Privacy is a core requirement
[ ] The dataset per user is manageable on-device (< few GB)
[ ] You can invest in sync infrastructure complexity
If 4+ boxes are checked, local-first is likely the right choice.
Getting Started: Minimal Local-First App
import * as Y from 'yjs';
import { IndexeddbPersistence } from 'y-indexeddb';
import { WebsocketProvider } from 'y-websocket';
// 1. Create a CRDT document
const doc = new Y.Doc();
// 2. Persist locally (works offline)
const localProvider = new IndexeddbPersistence('my-app', doc);
// 3. Sync with peers when online
const remoteProvider = new WebsocketProvider(
'wss://my-sync-server.com',
'my-room',
doc
);
// 4. Use shared data types
const todos = doc.getArray('todos');
// This works offline, syncs when connected, merges without conflicts
todos.push([{ text: 'Buy groceries', done: false }]);
// 5. Observe changes (from local or remote)
todos.observe((event) => {
renderTodoList(todos.toArray());
});
Key Terminology
| Term | Definition |
|---|---|
| Replica | A full or partial copy of the dataset on a device |
| CRDT | Conflict-free Replicated Data Type; merges without coordination |
| Sync engine | The layer that detects changes and propagates them between replicas |
| Optimistic update | Applying a mutation locally before server confirmation |
| Causal ordering | Ensuring operations are applied in an order consistent with causality |
| Tombstone | A marker indicating a deleted item (needed for sync) |
| Version vector | A data structure tracking the latest known state from each replica |
| Partial replication | Syncing only a subset of the full dataset to a device |
Install this skill directly: skilldb add local-first-skills
Related Skills
crdt-fundamentals
Teaches Conflict-free Replicated Data Types (CRDTs), the mathematical foundation for local-first sync. Covers how CRDTs guarantee eventual consistency without coordination, the difference between state-based and operation-based CRDTs, and practical implementations of G-Counter, PN-Counter, LWW-Register, OR-Set, G-Set, and RGA (Replicated Growable Array). Includes causal ordering, vector clocks, and guidance on choosing the right CRDT for your data model.
electric-sql
Teaches ElectricSQL, a Postgres-backed local-first sync framework. Covers the Electric architecture where Postgres is the source of truth and data syncs to local SQLite databases on client devices via shape-based partial replication. Includes shape definitions, live queries, offline-first patterns, conflict resolution with rich CRDTs, integration with React and Expo (React Native), deployment patterns, and migration strategies.
indexeddb-patterns
Teaches IndexedDB patterns for local-first web applications, using Dexie.js as the primary wrapper library. Covers schema design and versioning, creating indexes for efficient queries, transaction patterns, performance optimization (bulk operations, pagination, lazy loading), migration strategies for schema evolution, storage quota management, data export and import, and integration patterns with sync engines and reactive frameworks.
local-first-auth
Teaches authentication and authorization patterns for local-first applications that must work offline. Covers offline-capable auth with cached tokens, permission sync and local enforcement, encrypted local storage for sensitive data, key management with device-bound keys, device authorization and revocation, multi-device identity linking, end-to-end encryption for synced data, and secure patterns for handling auth in disconnected environments.
sync-engine-architecture
Teaches how to design and build a sync engine for local-first applications. Covers the operation log as the foundation, conflict resolution strategies (last-write-wins, operational transform, CRDTs), server reconciliation patterns, partial sync for large datasets, bandwidth optimization techniques, version vectors and causal consistency, clock synchronization, and practical implementation patterns with code examples.
yjs-sync
Teaches building local-first collaborative applications with Yjs, the most widely adopted CRDT library for JavaScript. Covers the Y.Doc document model, shared types (Y.Map, Y.Array, Y.Text, Y.XmlFragment), the awareness protocol for presence and cursors, persistence and sync providers (WebSocket, WebRTC, IndexedDB), integrating with editors like ProseMirror/TipTap/CodeMirror/Monaco, undo/redo management, and performance optimization patterns.