Notion published in 2024 that it moved its browser client's cache into SQLite compiled to WebAssembly, reporting page navigation about 20% faster across modern browsers and about 33% faster for users in India, after the same caching had already shipped in its desktop applications. What problem forces a database into the browser, what does that cost, and where would copying it be a mistake?
Show the full answer Hide the answer
The situation they were in
A workspace document product assembles a page from a large number of small records - blocks with parent and child relationships, several levels deep. A response cache can store what the server sent; it cannot answer "give me this page's block subtree" unless that exact response is already in it. So navigation keeps going back to the server for data the client has already seen in another shape, and every hop pays the user's round trip.
What they chose
SQLite compiled to WebAssembly, running in the browser, holding the cached records as queryable rows. Notion's write-up gives the mechanics: persistence through the Origin Private File System, and because the WASM build can only reach OPFS from a worker, the database runs in a Web Worker with Comlink carrying messages to and from the main thread.
The reported result is about 20% faster page navigation in all modern browsers, and larger where the network is slower - about 28% in Australia, 31% in China and 33% in India.
Why it fit their constraints
The shape of those numbers is the tell. A gain that grows with network distance is a gain from removing round trips, not from faster code. Their sessions are long and navigation-heavy, so a client-side database is amortised over dozens of navigations; and they had already run the design in their desktop applications, so the browser work was a port of something validated, not a bet.
What it cost
- Download and start-up. A SQLite WASM build is on the order of a megabyte uncompressed, several hundred kilobytes compressed - a real addition that has to stay off the critical path.
- Every query is asynchronous. OPFS is worker-only, so what used to be a map lookup is now a message hop, and code that assumed synchronous cache reads must be rewritten.
- Concurrency becomes yours. Several tabs sharing one database on disk is a single-writer problem you now own; the usual answer is to let only one tab write.
- Migrations run on users' devices. A schema change is a deployment to machines you cannot roll back, against data you cannot inspect.
- Storage is not yours either. The browser can evict it, and the cache must be correct when it comes back empty.
When not to copy this
Copying this would be a mistake in four situations.
- Screens that are a handful of flat responses. A normalised in-memory cache plus HTTP caching captures most of the win for none of the cost. Reach for a query engine when the client needs relational questions answered over data it already holds - subtrees, joins, local filters - not when it needs the same response again.
- Short sessions. A checkout flow or a marketing page never navigates enough to repay the download. The cache has to be amortised, and one page view does not amortise anything.
- A user base on slow storage. Local flash on a low-end phone can be slower than a nearby server, so a design that assumes disk beats network can regress the users you most wanted to help. Measure by device class before and after, not in aggregate.
- Validating it on your users. They moved a proven design into a new runtime. Porting is a different risk from inventing.
The decision rule that generalises: a client database earns its keep when the client needs relational queries over data it already has, sessions are long enough to amortise the runtime, and navigations per session are high. Fail any one of those and a response cache is the better engineering.