How it works
A search index is mostly reads. Rangefind does all the writing at build time — postings, documents, facets, and geo trees packed into content-addressed files — so query time is nothing but a few small, cacheable HTTP range requests against your own static hosting.
Index your content
Point the crawler at any built static site, or feed JSONL through a schema with weighted fields, facets, numbers, geo points, and vectors.
npx rangefind build ./dist
Deploy plain files
The index is a directory of immutable packs plus one small manifest. Any static host or CDN that supports range requests will do — most do.
rsync -r dist/ your-host:
# or: netlify / pages / s3 / r2…
Search from the browser
The runtime fetches the manifest, then range-reads exactly the term postings and documents a query touches. Warm queries are often a single request — or zero.
const rf = await createSearch({ baseUrl: "/rangefind/" });
await rf.search({ q: "byte ranges" });
A complete engine, not a toy
Everything below ships in the same static index and the same zero-dependency runtime — no plugins to bolt relevance on later.
Real relevance
BM25F field weighting with phrase and proximity signals, impact-ordered postings, and early termination for fast top-k.
Typo correction
Bounded vocabulary probes rescue misspelled queries only when first-page results are empty or weak — no fuzzy tax on good queries.
Facets & filters
Keyword facets with exact-or-flagged counts, plus typed numeric, date, and boolean doc-values for filtering and sorting.
Search-as-you-type
Diacritic-folded prefix autocomplete with popularity weights; the first keystroke costs one small fetch from a precomputed hot list.
Geo search
Static KD trees with bounding-box and radius filters, exact nearest-neighbor sort, and distance boosts — proven on 33M OSM places.
Semantic & hybrid
An int8 IVF vector index with cosine top-k and reciprocal-rank fusion against the text lane. No vector database.
20+ languages
Per-document language detection, light stemmers and stopwords, script-aware folding, and deterministic CJK bigrams — frozen into the manifest.
Incremental updates
Publish small delta generations over an existing index; unchanged packs keep their names and their CDN cache entries.
Measured, not promised
| corpus | docs | index | cold query |
|---|---|---|---|
| French Wikipedia | 2.75M | 5.3 GB | 7–66 ms |
| English Wikipedia | 7.19M | 9.1 GB | 128 ms · 2.3 MiB |
| OSM Québec (geo) | 6.1M | 9.5 GB | 5–13 ms nearest |
| OSM United States | 32.8M | 11 GB | 7–16 ms nearest |
Every number comes from committed benchmark scripts you can rerun, with quality checked against Lucene and Tantivy oracles. See the methodology →
Planet-scale, still static
Geographic sharding splits a corpus into independently built and updated region indexes that merge into one engine at query time — with rankings proven identical to a single monolithic build. All of OpenStreetMap fits in ~310 shards, updates nightly as kilobyte-sized deltas, and serves from object storage for the price of a coffee.
Drops into your stack
Eleventy
eleventy-plugin-rangefind indexes the built site and registers a search shortcode. This site uses it.
Astro
rangefind-astro hooks astro:build:done and ships a <RangefindSearch /> component.
Docusaurus
docusaurus-plugin-rangefind replaces hosted search on docs sites with your own static index.
MkDocs
mkdocs-rangefind on PyPI builds the index after mkdocs build and injects the widget.
Hugo & anything else
Copy-in layouts for Hugo — or run npx rangefind build ./public after any generator. If it makes HTML, it's indexable.
Node / MCP / SSR
rangefind/node runs the same engine server-side over local directories or remote indexes, with disk caching.
Two ways to start
Have a built site?
Index the HTML you already generate, then mount the web component. Titles, headings, and body text are extracted automatically; data-rangefind-* attributes refine scoping, facets, and metadata.
npx rangefind build ./dist
<script type="module" src="/_rangefind/rangefind-search.js"></script>
<rangefind-search src="/rangefind/" hotkey></rangefind-search>
Have structured data?
Describe fields, weights, facets, and geo points in a config; feed JSONL; query with the full API — filters, sorting, geo, vectors, and pagination included.
npx rangefind build --config rangefind.config.json
import { createSearch } from "rangefind";
const rf = await createSearch({ baseUrl: "/rangefind/" });
const hits = await rf.search({
q: "harbor",
facets: ["topic"],
geo: { near: { lat: 46.8, lon: -71.2, radiusMeters: 5000 } }
});