Linear stores your whole database in the browser — here is how
Linear co-founder Tuomas wrote the sync engine before any product feature — an almost unheard-of startup decision. The payoff: every click applies locally in IndexedDB first; the server only hears about it afterward, making the app feel native while hiding the network entirely.
Mutations hit a local IndexedDB store first; the server learns via WebSocket only after the screen repaints. Every model property is its own MobX observable, so one field change rerenders one cell.
Four bundler migrations cut shipped JS 50%, compressed size 30%, Safari first-paint 59%. A service worker precaches ~1,200 assets silently after first login.
The thread is unconvinced. Users report slow search, runaway memory, minutes-long sync-lock. The real question: is optimistic UI a performance win or lying about data safety?
Linear's browser-database architecture, dissected

The performance.dev breakdown of Linear opens with a confession from the product's own co-founder: "Literally the first lines of code that I wrote was the sync engine, which is very uncommon." That single decision — building the data synchronization layer before anything users could click — defines everything else about how the app performs in the thread.
The database lives in your browser, not on a server

The architectural inversion is straightforward to state, harder to execute. Traditional project management tools follow the same loop: user clicks → HTTP request → server query → browser repaint, with spinners filling the waiting. Linear eliminates that loop by keeping a full copy of the user's workspace data in IndexedDB. When you change an issue status, three things happen simultaneously: the MobX observable updates (repainting the UI immediately), the mutation writes to a durable transaction queue in IndexedDB, and the delta queues for WebSocket transmission. The network is never consulted before the screen changes.
On boot, the app hydrates from IndexedDB into an in-memory MobX object pool. There is no "loading issues" state because the data already exists on the machine. For large workspaces, the heaviest tables (Issue and Comment) lazy-hydrate on demand — the article claims a 10,000-issue workspace boots approximately as fast as a 100-issue one because startup cost tracks workspace structure, not size.
Four bundler migrations and a service worker holding 1,200 assets

The 21 MB of minified JavaScript that one commenter flags as suspicious is aggressively split into hundreds of route-level chunks loaded on demand, targeting only modern browsers (no legacy polyfills). The four bundler migrations — Parcel to Rollup to Vite to Rolldown — each chased the same goal: less shipped code. The measured results: 50% less code, 30% smaller after compression, cold-cache loads 10–30% faster, Safari first-paint improved 59%, memory down 70–80%.
Rather than letting the browser discover chunk dependencies sequentially through JavaScript parsing, Linear declares the full dependency graph as <link rel=modulepreload> entries in the HTML. The crossorigin attribute on preload links matches the entry script, letting the browser reuse cached fetches rather than treating preload and import as separate requests. A service worker precaches roughly 1,200 hashed assets after the first login; by the time the user navigates anywhere, the full app is already resident.
Before any JavaScript executes, inlined CSS paints the correct loading state — sidebar width, border radius, dark-mode tokens — directly from localStorage, so the shell is already correctly themed when the first bundle arrives.
Granular observables: why one field change doesn't repaint the list

Every property on every model is its own MobX observable. Components wrapped in observer() know exactly which fields they depend on. A status change on one issue triggers re-renders only for components reading that field — not the parent list, not the sidebar. The article argues fifty concurrent updates from multiple users equals fifty individual cell repaints rather than fifty full list repaints. Removing any one of the three pillars (local database, optimistic writes, granular observables) degrades the whole: local data without optimistic writes still shows spinners on save; optimistic writes without granular observables cause jank on every server confirmation.
Animations follow the same discipline: only composited properties (transform, opacity) get transitions — never layout-triggering properties like height or margin. Hover states appear instantly and fade out over 150ms, mimicking macOS native rhythm. The timing tokens sit well below industry norms: --speed-quickTransition: .1s, --speed-regularTransition: .25s.
The thread flips the narrative: is this speed, or the illusion of it?
"This is bizarrely laudatory. The app is fast because it is not correct. The user has no way to know if their view is consistent with any other user's view… Linear developers seem to believe that silent data loss is an acceptable cost for maintaining the illusion of speed." — a long-time HN contributor with 24k karma
That lands as the sharpest reversal in the thread. The article presents optimistic updates as a performance technique; the critic reframes them as a correctness hazard. He is not alone.
"My Linear client gets stuck 'syncing' all the time, completely blocking all interaction. I'd rather have loading spinners than getting, essentially, locked out of the whole app for 10 minutes." — an active HN user
A third voice — the co-founder of Rocicorp, the team behind the Zero sync engine — arrives not to rebut but to recruit:
"If you're interested in this kind of experience for your application, check out Zero (https://zero.rocicorp.dev/). We also list some alternatives here… If you're interested in how these things work internally, check out the Replicache design doc: https://doc.replicache.dev/concepts/how-it-works. Replicache was the predecessor to Zero and the core protocol still works the same way." — Aaron Boodman, co-founder of Rocicorp
Boodman's comment is the thread's quiet pivot: local-first sync is not Linear's proprietary magic — it is a pattern with a growing ecosystem of alternatives.
A co-founder of Clor punctures the article's 300ms-vs-milliseconds benchmark directly:
"There's no solving the problem of a large RTT between an HTTP client and server when it's due to the speed of light. But what you can do is locate the backend near users and make sure it's fast. For example, it's very possible to run a web app backend within ~10ms RTT of most users and have the backend render responses within ~10ms too. In other words, you can absolutely create a traditional CRUD app where doing the same thing takes more like 30ms, not 300ms." — co-founder at Clor
What actually changed — and what the article does not say
The article is a genuine technical document: the module preload strategy, the MobX granularity story, the four-bundler history, the animation timing tokens — these are real, specific, and verifiable. What it elides is the failure mode. A software engineer in the thread who spent months building a similar sync engine catalogues what goes wrong when two users are offline simultaneously: last-writer-wins conflicts, deleted-then-edited items, reordering divergence, schema drift on reconnect, 5,000 queued mutations firing at once. "Whatever can go wrong in an online-first world, can also go wrong in an offline first world but you might get informed of that all at once at a later time — or not at all."
The sync engine that Tuomas wrote on day one is not a solved problem dressed in fast UI. It is a hard distributed-systems problem dressed in fast UI. The article explains why Linear feels fast. The thread explains why that feeling costs something.
Get breakdowns like this every day
The top Hacker News stories, unpacked — by email, Telegram, or RSS.
Qwen 3.7 Max costs a fraction of Claude and the gap is closing
A self-described engineer and sci-fi nerd argues that Anthropic and OpenAI have become 'OnlyFans economies' — extracting subscription money from loyal fans through parasocial brand attachment rather than genuine model superiority. Chinese open models, especially Qwen 3.7 Max, have crossed the threshold of good enough.