❯ parkerjones.dev

Databases in the Browser

2026-06-29
Parker Jones and Claude Opus 4.8 in software
#wasm , #sqlite , #duckdb , #surrealdb , #local-first , #browser and #search
13 minute read

Part 1 laid out the heresy: a static site can be genuinely dynamic if you stop reaching for a JavaScript framework and let a real database — running in the reader's browser, with no server at all — do the work.

This post is the field guide for that idea: the actual landscape of databases you can run in a browser tab, before I show you the experiment where I tried to live by it.

I went down this road for a concrete reason — this blog runs a real database client-side, in WebAssembly, to power its tag and category pages (that's the next post). But before I write up my experiment, I want to do the thing I wish more "look what I built" posts did: survey the actual field first. What's genuinely possible in mid-2026, what's mature versus what's a demo, and what the tradeoffs really are. The crux of this blog is learning as I go, and you can't tell whether your own hack was clever or foolish until you know what everyone else has already figured out.

A second confession is baked into that one. I started this series a while back, drafting with OpenAI's models — o1, GPT-4 — and then let it stall. I'm picking it back up with Claude Opus 4.8 as a writing partner, because the other experiment running through this blog is inserting AI into my own workflow: figuring out what it's good for, where it leads me astray, and how to find my own voice by arguing with a model until the words are actually mine. So I'll be transparent about it as I go. When the AI helped me build something sharp, I'll say so; when it helped me cheerfully overcomplicate things — like the database you'll meet in the next post — I'll say that too. Sometimes the experiment doesn't make sense, and naming that is the whole point.

So: a field guide. Then, in the next post, I'll show you the experiment this blog actually runs — and the question underneath it that turned out to be far more interesting than search.

Why put a database in the browser at all?

The reflexive answer from a backend engineer is "you wouldn't." Databases belong on servers; the browser gets JSON over an API. But there's a real thesis underneath the trend, and it's worth taking seriously:

That list isn't mine — it's most of the argument for local-first software, which is the intellectual backdrop for this whole space.

The local-first movement

The phrase comes from a 2019 Ink & Switch essay, "Local-first software: you own your data, in spite of the cloud" (Kleppmann, Wiggins, van Hardenberg, McGranaghan). It lays out seven ideals worth knowing, because almost everything in this post is chasing some subset of them:

  1. No spinners — the UI responds at local speed.
  2. Your work is not trapped on one device — it syncs.
  3. The network is optional — offline is a first-class state, not an error.
  4. Seamless collaboration — multiple people, no lock-the-file pain.
  5. The Long Now — your data outlives the vendor.
  6. Security and privacy by default — local storage, ideally end-to-end encrypted.
  7. You retain ultimate ownership and control — it's your data, on your disk.

The hard part of that list is #4 — collaboration without a central server arbitrating writes. The answer the field landed on is CRDTs (conflict-free replicated data types), data structures that merge concurrent edits deterministically. The two libraries you'll actually encounter are Yjs (the de-facto standard behind a lot of collaborative editors) and Automerge (JSON-shaped documents with full change history).

CRDTs solve merging; you still need to move the bytes and store them. That's the layer where a small industry has appeared:

ProjectWhat it isSync model
ElectricSQLSync layer for Postgres → clientStreams Postgres changes to local replicas
RxDBLocal-first NoSQL DB for JSOffline-first; pluggable replication
PouchDBJS database that speaks CouchDBMulti-master HTTP replication
EvoluLocal-first on SQLite-WASMEnd-to-end encrypted, CRDT merge
TriplitFull-stack sync databaseReal-time WebSocket sync, offline mode
JazzLocal-first app framework on CRDTsReal-time sync, row-level permissions

I'm not endorsing any of these for a blog — most are aimed at collaborative apps far heavier than a personal site. They're here because they're the context. When someone says "database in the browser," they might mean any of three very different things: a sync framework (the table above), an embedded database engine compiled to WASM (next section), or a purpose-built search index (the section after that). Conflating them is how people end up shipping 30 megabytes to render a search box.

The contenders: real databases compiled to WASM

This is the part that still feels slightly illicit. You can take a database written in C or Rust, compile it to WebAssembly, and run the actual engine — query planner and all — inside a browser tab. Four are worth knowing.

SQLite-WASM

The one to start with. The SQLite project ships an official WASM build (@sqlite.org/sqlite-wasm), and there are older community efforts — sql.js (in-memory, around since 2014), wa-sqlite, and the now-retired absurd-sql.

The whole game with SQLite-in-the-browser is persistence, because a tab has no filesystem. Your options, roughly in order of modernity:

Realities to budget for: the WASM binary is several megabytes, Safari has historically been the buggy one, and OPFS isn't available in private-browsing mode. None of that is fatal — it's just the stuff nobody mentions until you hit it.

DuckDB-WASM

DuckDB-WASM takes DuckDB — the in-process analytics engine — and runs it in a browser tab. The trick that makes it special is HTTP range requests — point it at a Parquet file on a CDN and it reads only the columns and row-groups your query touches, instead of downloading the whole file. It's how tools like Observable do interactive analytics over remote datasets with no backend.

It's production-grade and genuinely impressive. The constraint to remember: it's analytical and ephemeral. There's no persistence story like SQLite's — you load data in, you query it, it's gone on refresh. That's the right shape for dashboards and data exploration, the wrong shape for "remember this user's notes."

PGlite

PGlite is the audacious one: real Postgres, compiled to WASM, in about 3MB gzipped — no Linux VM, no container, just Postgres-in-process. It persists to IndexedDB in the browser, supports live/reactive queries, and — the part relevant to this series — runs pgvector, so you can do vector similarity search entirely client-side. It's built by the ElectricSQL folks, so it pairs naturally with their sync layer.

The honest status: it's still pre-1.0 / alpha. The capability is startling; I wouldn't bet a production app on it yet. For experiments and offline-first prototypes, it's a remarkable thing to have.

SurrealDB-WASM

This is the one I actually used here, so I'll be precise about its reality. SurrealDB ships a WASM build of its engine, and as of early 2026 the separate surrealdb.wasm repo was folded into the main JavaScript SDK. The thing to understand is which storage engines survive the trip to WASM:

So in a browser you get an in-memory or IndexedDB-backed SurrealDB speaking full SurrealQL. Whether that's a good idea for a static blog is a question I'll cheerfully tear into in the next post.

A quick map of maturity

Because "you can run X in the browser" and "you should ship X to readers" are very different claims:

EngineBrowser persistenceMaturity (mid-2026)Good for
SQLite-WASMOPFS / IndexedDB / memoryMatureGeneral embedded storage, offline apps
DuckDB-WASMNone (ephemeral)MatureAnalytics over remote Parquet, dashboards
PGliteIndexedDB / memoryAlphaPostgres features client-side, pgvector demos
SurrealDB-WASMIndexedDB / memoryMixed (mem/idb ok)SurrealQL without a server; experiments

The plot twist: for a blog, do you even want a database?

Here's the uncomfortable thing I learned by doing this the hard way. If your actual goal is search on a static site, a general-purpose database is almost certainly the wrong tool. There's a whole category of software built for exactly this, and it's better at it:

The distinction that matters: prebuilt index (Pagefind, Lunr, tinysearch — work happens at build time, the client just looks things up) versus in-browser indexing (Orama, FlexSearch, MiniSearch — the client builds the index on load). For a blog, prebuilt wins, because the content only changes when you rebuild the site.

I'm planting a flag here because it sets up the real question. For search, a database in the browser is the wrong tool — Pagefind wins, and I'll happily concede the point. Which leaves something more interesting hanging in the air: if not search, what is a real database in the browser actually good for? That's where the next post goes.

The interesting frontier: semantic search, client-side

There's one place where the database-in-the-browser idea stops being over-engineering and starts being genuinely new: semantic search with no backend.

The pattern is now well-trodden enough to describe crisply:

  1. At build time, embed every chunk of your content into vectors using an on-device model. Transformers.js (Hugging Face's in-browser ONNX runtime) runs models like all-MiniLM-L6-v2 to produce 384-dimensional embeddings. Write them to a static JSON file.
  2. At runtime, load the same small model in the browser, embed the query, and compute cosine similarity against the prebuilt vectors. No API key, no server, nothing leaves the tab.

The vectors can live in plain JSON, or in something fancier: sqlite-vec (a tiny vector-search extension that runs anywhere SQLite does, WASM included) or PGlite's pgvector. Supabase's in-browser semantic search demo wires PGlite + pgvector + Transformers.js into exactly this stack, and SemanticFinder is a slick frontend-only example you can play with.

The cost to be honest about: the embedding model is a ~25MB download — the model_quantized.onnx Transformers.js pulls by default; the full-precision model.onnx is ~90MB — cached after first load, but it dwarfs a Pagefind index. So semantic search in the browser is worth it when keyword search genuinely fails you — synonym-heavy domains, "how do I fix this" questions that don't share words with the answer — and overkill for a fifty-post blog where Ctrl-F energy is plenty.

It's a genuinely new capability and a tempting one — but for a blog it's a 25MB answer to a question Pagefind solves in 50KB. I file it under "things worth trying on a corpus where it would actually matter," not "things this blog needs" — a rabbit hole for its own future series, not this one.

Where this series goes next

So that's the lay of the land: a maturing set of real database engines you can run in a tab, a local-first philosophy giving them a reason to exist, and — for the specific job of blog search — a category of purpose-built tools that quietly outclass all of them.

Which is exactly why the next post isn't about search. The thing that actually pulled me in — the reason I shipped a real database to your browser instead of a search index — was a different and more interesting idea: getting a genuinely dynamic application out of a static site — no SPA, no server, free hosting — by using a client-side database as the engine of the frontend's application state, in place of a heavy JavaScript state-management library. Part 3 is that experiment — SurrealDB compiled to WebAssembly, seeded from a SQL dump I smuggle through a <pre> tag, driving page state in the tab — and an honest accounting of what worked, what I never finished, and whether a database can credibly stand in for your state library.

The fun of doing this in public is that I don't know the answer yet. That's the point.

— Parker Jones, parkerjones.dev