Skip to main content

Command Palette

Search for a command to run...

Tech Tuesday: Building a Local-First Search Engine with Web Workers

How Genealogix searches thousands of encrypted records in the browser without freezing the UI.

Published
3 min read
Tech Tuesday: Building a Local-First Search Engine with Web Workers
R
🧠 Software Engineer - Coder for decades: C#, JS, React & much more 💡 Exploring clean, joyful, intuitive code

Welcome back to Tech Tuesday!

If you look at any major genealogy platform today, the search bar is the center of the universe. Users expect to type in "John Smith Brooklyn" and instantly see a filtered list of census records, life events, and family members.

When you have a massive server farm running an Elasticsearch cluster, this is easy. But Genealogix has a strict zero cloud database policy to protect user privacy. All of your family history lives locally in your browser’s IndexedDB.

So, how do you run a full-text search across thousands of complex, encrypted JSON objects entirely on the client side? Here is how we engineered the Genealogix search engine.

The Problem: The Single-Threaded Bottleneck

JavaScript runs on a single main thread. This thread is responsible for everything: rendering the React components, handling your mouse clicks, and running your D3.js family tree visualizations.

When a user types a query into the Genealogix search bar, the app needs to:

  1. Fetch all the records from IndexedDB.

  2. Tokenize the search query (breaking it into searchable chunks).

  3. Run a matching algorithm across names, dates, places, and notes.

  4. Score and sort the results by relevance.

If we ran this heavy computational math on the main thread, the entire application would freeze for a few seconds on every single keystroke. The React UI would lock up, and the user experience would feel broken.

The Solution: Web Workers to the Rescue

To keep the Genealogix interface buttery smooth, we had to move the search engine off the main thread entirely. We accomplished this using Web Workers.

In our architecture, we built a dedicated fullTextSearch.worker.ts file. A Web Worker acts like a background processor sitting behind the scenes in your browser.

Here is the flow:

  1. The Hand-off: When a user types in the search bar, the React UI doesn't try to search anything. It simply packages the query string and sends a quick message to the Web Worker.

  2. The Heavy Lifting: The Web Worker receives the message, wakes up, and runs the heavy full-text search algorithm against the cached local data. It handles all the tokenization and string matching in complete isolation.

  3. The Return: Once the worker has a sorted list of matches, it posts a message back to the main thread with the results. React takes that list and instantly updates the UI panel.

Why This Matters

Because the Web Worker handles the CPU-intensive search process in the background, the main thread remains completely free. The user can keep typing, panning around their family tree, or opening menus, and the app will never stutter or drop frames.

By combining IndexedDB for local storage with Web Workers for background processing, we’ve matched the speed and UX of a modern, server-backed search engine—without ever letting your family data leave your device.

Are you utilizing Web Workers in your React applications? Let me know your favorite use cases in the comments below!