tech, developers, and the code underneath

issue 110· essay·

Local-first is finally practical

CRDTs got good, sync engines got boring, and the offline-capable app stopped being a research project.

"Local-first software" was a research paper and a manifesto in 2019. It is now a set of libraries you can actually ship, and the trade-offs have become concrete enough to reason about.

the pitch#

Data lives on the user's device. The application reads and writes locally, so every interaction is instant and works offline. Changes sync to other devices and other users in the background, merging automatically.

The properties you get:

  • No loading spinners. Reads are local. The interaction latency is the render time.
  • Offline works. Not degraded — fully functional.
  • The user owns their data, in a file, on their machine.
  • The server is a relay, not the source of truth, which makes it much simpler and much cheaper.

The properties you give up are the interesting part.

what actually got solved#

Conflict resolution. CRDTs — conflict-free replicated data types — let two devices edit the same document offline and merge deterministically without a central arbiter. The theory is old. What is new is that the implementations got fast enough and small enough to use.

The specific thing that changed: document sizes. Early CRDT implementations carried enormous metadata overhead — a text document could be ten times its content in tombstones and identifiers. Modern implementations use run-length encoding and columnar formats that bring the overhead down to something reasonable, and they can compact history.

Sync engines became a category. You no longer write the sync layer. Several production-grade options exist, some open source, some hosted, all with the mundane parts — auth, presence, storage, reconnection — handled.

Local databases in the browser. SQLite compiled to WebAssembly with a persistent backing store means the browser can host a real relational database with real queries. That removes the "but I need to query it" objection that killed a lot of earlier local-first attempts.

what is still hard#

Authorization. This is the big one and it does not have a clean answer.

In a server-authoritative system, permission checks happen in one place. In a local-first system, the client has the data — so either you sync only what the user may see (which requires the server to understand your permission model and partition accordingly), or you encrypt per-scope and manage keys.

Both are real work. Anything with fine-grained, dynamic, row-level permissions is a poor fit and you should be honest about that before you start.

Schema migration. Your users have data on their devices in an old schema and some of them will not open the app for eight months. You need migrations that run locally, that are forward and backward compatible, and that handle a client syncing after skipping four versions.

This is solvable and it is a discipline you must adopt from day one. Retrofitting it is very painful.

Server-side logic. Anything that must be authoritative — payment, inventory decrement, rate limiting, anything where the user must not be able to lie — still needs a server. Local-first is not "no backend." It is "the backend is smaller and does less."

History size. CRDTs accumulate operation history. For a long-lived document with many edits, that grows. Compaction helps and it has consequences for concurrent editing across the compaction boundary.

where it fits#

Excellent fit: note-taking, editors, design tools, project trackers, single-user or small-team collaborative documents, anything where a user has a personal working set and expects it to be fast.

Poor fit: anything with a large shared dataset the user only sees a slice of, anything requiring server-authoritative state, anything with complex dynamic permissions, anything regulated in a way that prohibits data on client devices.

Mixed: most business applications, where some of the data is personal working state that should be local and some is shared authoritative state that should not be. The hybrid is more work than either pure approach and is usually correct.

the reason to care#

The default web application architecture — every interaction is a network round trip to a server that queries a database — produces software that is slower today than desktop software was in 1998, on hardware that is thousands of times faster.

Everyone has normalized this. Users have not; they just do not have a word for why the app feels bad.

Local-first is the architecture that fixes it, and it is now practical enough that "we cannot do that" has stopped being true.

Dom, January 22, 2026

get README in your inbox

One dispatch, no noise. Tech and developer news, plus the occasional long piece on the craft.

subscribe →