How Khoji works
Traditional site search sends every query to a server that holds a copy of your content. Khoji inverts this: the index is built and queried inside the visitor's browser. Here is the full pipeline.
1. Discovery
On first use, Khoji fetches your sitemap.xml(following sitemap-index files up to two levels deep) and collects same-origin URLs up to your plan's page limit. Without a sitemap it falls back to harvesting links from the current page.
2. Extraction and chunking
Pages are fetched four at a time from your own origin — the visitor is effectively pre-loading your site. Khoji strips navigation, scripts, headers and footers, prefers <main> or <article> content, and splits the text into sentence-aligned chunks of roughly 480 characters.
3. Instant keyword index (BM25)
Each chunk goes into an in-memory BM25 index — the same ranking family used by Elasticsearch. This is available within seconds, so search works immediately even before any machine learning happens. Partial typing gets prefix-match credit, which makes search-as-you-type feel natural.
4. Semantic embeddings (WASM)
In a background Web Worker, Khoji loads a quantized all-MiniLM-L6-v2 embedding model (~23 MB, cached by the browser after first load) through transformers.js and ONNX Runtime WebAssembly. Every chunk is converted into a normalized 384-dimensional vector. Your visitor's CPU does this once, then the vectors are stored.
5. Hybrid ranking
A query is scored two ways: BM25 for lexical matches and cosine similarity against chunk vectors for meaning. Scores are blended (45% lexical, 55% semantic), deduplicated per page, and the best chunk becomes the snippet. This is why searching "refund my order" finds a page titled "Returns policy" even though the words don't overlap.
6. Caching
Pages, hashes and vectors persist in IndexedDB. On the next visit, Khoji re-checks page hashes and re-embeds only what changed. A repeat visitor gets a warm, fully semantic index in milliseconds.
Performance characteristics
| Site size | Keyword ready | Semantic ready (first visit) |
|---|---|---|
| 30 pages (blog) | ~2 s | ~15–30 s |
| 200 pages (docs site) | ~5 s | ~1–3 min in background |
| 2,000 pages (Pro) | ~20 s | background, cached forever after |
All of this happens off the main thread — your site's Core Web Vitals are unaffected, and the widget itself is a 14 KB script that loads async.