Skip to content

Jonwhitefang.uk

Stardate 2026.193 · systems nominal
All log entries
Software12 July 2026

Full-text search without a server

The Starlog Database has a proper search box: full-text search, filters, stardate lookup, across one thousand one hundred and eighty-eight log entries. What it doesn't have is a server. This whole site is a Next.js static export on Cloudflare, a folder of files with delusions of being an application, and I'd decided early on that no single feature was going to be the one that made me start running a backend. I've written about building the database already; this is the other half of the story, the part for anyone with a static site, a pile of data, and search envy. The short version is that the search engine lives in your browser, and it turns out that's not a compromise, it's the design.

The respectable options did exist. Cloudflare would happily have sold me a little database with real full-text search behind an API, and there's a tool called Pagefind whose entire job is search on static sites, if I'd been willing to render every log entry as its own page — thousands of routes for a dataset that fits in one file. Both fell to the same arithmetic: the entire dataset is a JSON file of about half a megabyte, under 120 kilobytes compressed on the wire, and the search library, FlexSearch, is about six kilobytes. When the whole problem weighs less than a decent photograph, standing up infrastructure for it isn't engineering, it's ceremony. So the deal became: open the page and your browser downloads the records and builds the index itself, in under a second, on your machine. Never open the page and you download nothing at all.

The searching itself is the easy part, which is not something I expected to write about a search engine. FlexSearch gets the text of every entry, tokenized so that partial words match while you're still typing them, with the input debounced just enough that it isn't searching your hesitation. The one genuinely fun bit of design is that stardates are first-class citizens: type something shaped like a stardate and it's matched against the stardate field first, ranked above the full-text results, because someone typing 41153.7 does not want prose that happens to contain those digits. The one trap worth shouting about is the bundler. The helpers module that knows about the data deliberately never imports the data, because the moment it does, half a megabyte of Starfleet's records gets baked into the page's JavaScript and ships to everyone on every visit. The dataset stays a plain file, fetched when wanted. Keep those two worlds apart or the lazy loading is a polite fiction.

What actually cost me time was the part I'd assumed was free: the URL. A search page without shareable links is a toy, so the query and filters live in the query string. But a static export renders every page once, at build time, with no knowledge of anyone's query string — so the first paint is always the empty, param-less page, and React will complain, correctly, if the client's first render disagrees with the server's. The fix is to render empty, read the URL once after mounting, and accept one extra render as the toll. Writing state back is its own small minefield: done naively, every keystroke becomes a browser history entry and the back button turns into a tape rewind of your typing. At one point the write-back stood accused of racing the read on load, and the only way to settle it was instrumenting the history API and watching the calls go past — which acquitted it of the race and still turned up something worth fixing, comfortably the most efficient way I've been wrong all year.

The honest sales pitch, then: if your dataset fits in one file you can compress to something reasonable, client-side search is not the budget option, it's the correct one. Every search on the Starlog Database happens on the visitor's own machine, at memory speed, with nothing to deploy, monitor, patch or pay for on my end. The break-even is real — this only works because the corpus is finite and I got it small — and if it ever grows by an order of magnitude, the same JSON seeds a proper database directly and no work is wasted. But the classic era of Star Trek is, conveniently, not producing new episodes. Some datasets have the decency to stay finished, and when they do, the browser is all the server you need.