Local-First Architecture: The Quiet Revolution Nobody Is Talking About
Optimistic UI has been a lie we told ourselves for years. Not a malicious one — a well-intentioned one. You click a button, the UI updates immediately, an...
27 Apr 2026

Optimistic UI has been a lie we told ourselves for years. Not a malicious one — a well-intentioned one. You click a button, the UI updates immediately, and a network request fires in the background. If it fails, you roll back and show an error. Clean, responsive, modern. Except it is still built on the same assumption it was always built on: the server is the real database, and the client is a polite guest who asks permission before touching anything.
Local-first architecture throws that assumption out entirely.
What actually changes
In the standard client-server model, the client is a view layer. It fetches data, renders it, and sends mutations back up. The server owns the state. The network is always in the path. Every interaction — every keystroke in a form, every checkbox toggle, every drag-and-drop — is at least one round trip away from being real.
Local-first inverts this. The client has its own database. Not a cache, not a store, not an in-memory Redux tree. An actual database, running locally, that your application reads and writes to directly. SQLite, running in the browser via WebAssembly. The server still exists, but its job is not to be the source of truth. Its job is to synchronise that local database with other clients.
The server becomes a sync relay, not an authority.
This is not a subtle shift. It changes where you write queries, where conflicts live, what "saving" means, and which part of the stack you trust most.
Why this is happening now
The idea of local-first software is not new. Ink & Switch published their manifesto on it in 2019. But the stack to actually build it in a browser at production quality only came together around 2025. The thing that changed: SQLite compiled to WASM became reliable enough to ship.
Running SQLite in a browser used to mean compromises — half-ported implementations, missing features, fragile persistence. The wa-sqlite and sql.js-httpvfs projects pushed the boundaries, but they were not things you built a product on. The SQLite WASM official port from the SQLite team changed that. It is the real thing, with the real query engine, persisted via the Origin Private File System API. That landed in browsers mid-2023, but the tooling around it took another year to stabilise.
At the same time, sync engines matured. ElectricSQL syncs Postgres data directly to SQLite on the client via a Postgres logical replication stream. Replicache provides a framework for building your own sync layer with a clear push/pull protocol. PowerSync targets mobile and web with a managed sync service. None of these were production-viable five years ago.
The user expectation piece matters too. People have used native apps on their phones for fifteen years. They expect zero-latency response. Optimistic UI approximates that feeling; local-first actually delivers it, because the write goes to a local database and the sync happens asynchronously in the background. If you lose internet for twenty minutes and come back, your changes are still there. The app never told you it failed.
CRDTs: how conflicts get resolved
The hardest part of any sync system is what happens when two clients edit the same data while offline and then reconnect. You have two diverged states and you need to merge them without losing either user's intent.
CRDTs (Conflict-free Replicated Data Types) are the mathematical answer to this problem. A CRDT is a data structure designed so that any two states can always be merged, regardless of the order the operations arrived in. The merge is always deterministic. There is no "last write wins" ambiguity or manual conflict resolution required.
The practical ones you will encounter are Last-Write-Wins registers (simple scalar values where a timestamp or logical clock determines the winner), OR-Sets (sets where you can add and remove items and concurrent operations merge cleanly), and sequence CRDTs like those used by Yjs and Automerge for collaborative text editing.
Yjs in particular is interesting because it is not just a research project. Linear uses a variant of it for collaborative issues. Notion's block model is conceptually similar. If you have used Google Docs, you have used something CRDT-like without knowing it.
The trade-off is that CRDTs are not free. They carry metadata — tombstones for deleted items, vector clocks, operation histories. A CRDT document that has been heavily edited is larger than the equivalent plain object. For most application data this does not matter. For real-time collaborative editing on large documents, it starts to.
What this means for your role
Frontend developers who adopt this architecture start doing things that used to belong to the backend. Writing SQL. Designing schemas. Thinking about indexes. Understanding transaction semantics. Deciding which tables sync fully and which sync partially based on the current user's permissions.
That last one is non-trivial. In a traditional app, the server filters what data the client receives. In local-first, you still need that boundary, but it lives in the sync engine's configuration rather than in API endpoint logic. ElectricSQL calls this "shapes" — declarative rules about which subset of the Postgres database a given client is allowed to sync. Getting shapes wrong means either leaking data or under-syncing data. It is a DBA problem wearing a frontend costume.
Backend developers, meanwhile, stop writing REST endpoints for every UI interaction. There is no POST /tasks/:id/complete in a local-first app. The client writes directly to the local database and the sync engine handles propagation. What backend developers design instead is the sync protocol: conflict resolution rules, permission shapes, consistency guarantees across clients, and the Postgres schema that feeds the sync engine.
It is not less work. It is different work, and in some ways more interesting. You are designing a distributed system, not an API.
The things that are still hard
Local-first is genuinely great for applications where the core interaction is read/write of structured data that belongs to a user or a small collaborative group. Task managers, note-taking apps, project trackers, collaborative documents, local-heavy mobile apps. Linear is the canonical example that people point to.
It is less obvious for applications with heavy server-side computation, real-time feeds of data from many sources (social media timelines, stock tickers), or strict regulatory requirements around where data lives and who can access it. SQLite in the browser is great for your own data. It is awkward for data that fundamentally lives on many other users' servers.
The offline story is also more complex than it sounds at first. An app that lets you edit a document offline is straightforward. An app that lets you book a hotel room offline is not — because "is this room still available" is a question only the server can answer. Local-first handles local consistency well. It does not help with global constraints that require a single authoritative check.
I am genuinely uncertain about how far this pattern scales into enterprise software. The tooling is still early. Most of the production case studies come from tools with a single-workspace model (one Postgres database, clearly scoped users). Multi-tenant SaaS at scale, with complex permission hierarchies and large shared datasets, has not been solved cleanly yet. ElectricSQL is working on it. I do not think the answer is obvious.
Worth learning now
The shift from "server is source of truth" to "sync is infrastructure" is real and it is happening. If you are building any kind of productivity tool, collaborative app, or mobile-first product in the next few years, you will encounter this pattern. Understanding CRDTs even superficially, knowing what ElectricSQL or Replicache actually do, and having a mental model of how local SQLite fits into a sync architecture — all of that will start mattering in design conversations soon.
The engineers who will be most valuable in this transition are not the ones who learned SQLite in a browser tutorial. They are the ones who understand the distributed systems reasoning underneath it: why eventual consistency is acceptable in some places and not others, how to think about conflict resolution as a product decision, not just a technical one, and when the complexity of a sync engine is worth it versus just using a fast API.
I break down patterns like this every week in Monday BY Gazar — one system design concept, explained with enough depth to use in a real conversation. And if you want to see the video version, Gazar Breakpoint on YouTube goes deeper on the implementation side.
Keep reading
- The Capacity Estimation Numbers Every Engineer Should Carry Into a System Design
- Notes From My First Cohort: System Design Is Trade-offs, Not Answers
- Write HLDs and ADRs in HTML or React, Not Markdown
- CRDTs: Conflict-Free Merging in Distributed Systems
- Race Conditions: What They Are and How to Handle Them
- SLO, SLA, SLI: What They Actually Mean and How to Use Them