The Local-First Manifesto
For fifteen years the industry told us that the cloud was the destination. Every app became a thin client for a database you could never touch, in a data center you would never see, governed by a terms-of-service you never read. This note is a long argument for the opposite premise: that the most durable, fastest, and most humane software keeps your data on your device first, and treats the network as an optimization rather than a dependency.
"A tool you cannot use without permission is not a tool. It is a subscription to someone else's tool."
Why "local-first" is more than offline mode
Offline mode is a feature. Local-first is an architecture. The distinction matters:
- Offline mode caches a slice of server state so a spinner appears less often. The server is still the truth; the cache is a courtesy.
- Local-first inverts the ownership. The device holds the full corpus. The server exists to synchronize copies between your devices and, when you choose, to publish a document to others.
The seven ideals — as first framed by the Ink & Switch essay that named the movement — are worth restating in plain language:
- No spinners. Reads and writes hit local storage, so they are instant.
- Your work is not trapped on one device. Sync fans your data out to every device you own.
- The network is optional. A plane, a tunnel, a dead Wi‑Fi router — none of it stops you.
- Collaboration without a central bottleneck. Merges happen at the edges.
- The Long Now. Your notes should outlive the company that made the app.
- Security and privacy by default. Data at rest is yours; sharing is a deliberate act.
- You retain ultimate ownership. Export is a right, not a favor.
How this notebook implements it
Every note you are reading right now was rendered from a copy sitting in your browser's IndexedDB, not fetched from a server on demand. The read path is deliberately boring:
// Simplified: the owner's read path never blocks on the network.
async function openNote(slug: string): Promise<Note | null> {
const local = await store.getNote(slug); // IndexedDB — microseconds
if (local) return local; // the common case
return fetchFromServer(slug); // cold cache / shared link only
}
Writes are optimistic. When you pin a note, the pin is true before the request leaves your machine:
async function pin(slug: string) {
await store.update(slug, { pinned: true }); // instant, local
outbox.enqueue({ op: 'pin', slug }); // durable queue
scheduleSync(); // invisible, background
}
If the tab closes mid-sync, the outbox survives. The next launch drains it. You never see a half-committed state, because the local copy is the state.
The hard part: identity boundaries
Local-first has a sharp edge that cloud-first apps never face: the data lives on a shared device. If two people sign into the same browser, the second person must never see the first person's corpus. The fix is not a permission check bolted onto the read path — a check that races the render always loses. The fix is structural: each account opens a physically separate local database, and signing out purges the store.
| Concern | Cloud-first answer | Local-first answer |
|---|---|---|
| Where is the truth? | The server | Your device |
| First read latency | A network round-trip | A memory read |
| Works on a plane? | No | Yes |
| Who can read the cache? | N/A (no cache) | Only the signed-in account |
| What happens on logout? | Cookie cleared | Cookie cleared and store purged |
When the cloud still earns its place
Local-first is not anti-cloud. It is cloud-as-a-feature. Three jobs are genuinely better centralized:
- Sync — reconciling copies across your laptop, phone, and tablet.
- Publish — turning a private note into a public URL like the one this note lives at.
- Share — handing a single document to a specific person without opening your whole notebook.
Notice that all three are opt-in verbs. The default is private and local. See also The Power of Local-First Apps for the shorter version of this argument, and Product Design Philosophy for the values underneath it.
Closing
The measure of a tool is what happens when the company behind it disappears. A cloud-first app becomes a login screen to nowhere. A local-first app becomes a folder of files you still own. Build for the second outcome.
Written on a train, with the Wi‑Fi off, saving after every sentence.