For the past decade, we've been told that everything belongs in the cloud. Your documents, your photos, your data—all of it should live on someone else's computer. But something is shifting.
The Problems with Cloud-First
The cloud-first approach has some fundamental issues:
- You're always dependent on a connection - No wifi? No work.
- Latency is unavoidable - Every action requires a round-trip to a server
- Your data isn't really yours - Companies can lock you out, change terms, or disappear
- Collaboration is an afterthought - Most cloud apps handle conflicts poorly
What is Local-First?
Local-first means your app works on your device first, with sync as an enhancement:
// Local-first: data lives on device, syncs when possible
const document = await localDB.get('doc-123');
document.title = 'Updated locally';
await localDB.put(document);
// Sync happens in background, doesn't block UI
sync.push(document);
Compare this to cloud-first:
// Cloud-first: every change requires network
const response = await fetch('/api/documents/123', {
method: 'PATCH',
body: JSON.stringify({ title: 'Updated' }),
});
// User waits for this to complete
if (!response.ok) throw new Error('Failed to save');
Real-World Examples
Some apps are already leading the way:
- Linear - Feels instant because it syncs in the background
- Figma - Real-time collaboration that works offline
- Obsidian - Your notes are markdown files on your computer
- Excalidraw - Drawings stored locally with optional sync
The Technical Foundation
Building local-first apps requires different tools:
CRDTs
Conflict-free Replicated Data Types handle merging changes from multiple devices:
import { Doc } from 'yjs';
const doc = new Doc();
const text = doc.getText('content');
// Changes from any device can be merged
text.insert(0, 'Hello from device A');
// Another device's changes merge automatically
Sync Engines
Tools like PowerSync, ElectricSQL, and Replicache handle the complexity of syncing:
import { PowerSyncDatabase } from '@powersync/web';
const db = new PowerSyncDatabase({
schema: AppSchema,
database: { dbFilename: 'app.db' },
});
// Write locally
await db.execute('INSERT INTO tasks (title) VALUES (?)', ['New task']);
// Sync happens automatically in background
Why This Matters Now
Several trends are converging:
- Better tooling - CRDTs and sync engines are maturing
- User expectations - People expect apps to work offline
- Privacy awareness - Users want control over their data
- Edge computing - Computing is moving closer to users
Getting Started
If you're building a new app, consider starting local-first:
- Store data in SQLite or IndexedDB on the client
- Use a sync engine to handle replication
- Design for offline-first, online-enhanced
- Handle conflicts gracefully with CRDTs
The future of software is local-first. The question is whether you'll build it or be disrupted by it.