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.
Looking for a free Google Maps API alternative?
Rangefind replaces the OSM-backed portion of many Google Maps integrations: place autocomplete, text and nearby search, forward and reverse geocoding, open-now constraints, inline place details, search along a route — and the route itself, with matrices and multi-stop itineraries. Queries run in the browser against your own static index, with no API key or per-request Rangefind fee.
Smaller migration
A tested adapter accepts familiar Google Places-style request shapes while preserving native Rangefind metadata.
Honest boundaries
Keep another provider for map tiles, reviews, ratings, photos, tolls, and other proprietary data. Traffic is a feed you supply, not a licensed dataset.
Predictable costs
Pay for build compute, storage, and CDN traffic—not a proprietary API charge attached to every user query.
It routes, too — and it still isn't a server
The same trick works on roads. A route graph packed into content-addressed objects answers driving routes, travel-time matrices, and optimized multi-stop itineraries from a bounded set of range requests — results proven edge-for-edge identical to a full-graph Dijkstra. Live traffic arrives through a provider contract, and PulseMesh implements it peer-to-peer, so nothing about where anyone drove reaches a database.
Routing & itineraries
Exact Held-Karp stop ordering up to 10 interior stops, 2-opt beyond. A 5-stop, 525 km round plans in ~410 ms cold — matrix, ordering, and four unpacked legs.
PulseMesh live traffic
Anonymous corroborated speed observations, ~42 bytes each, aggregated identically by every peer. No traffic server, no accounts, and a 90-second TTL as the entire retention policy.
PulseMesh threads
Follow one school bus or one courier from a link that is a key, not a URL to a server. The arrival time is routed on your own device, so the publisher never learns which stop you care about.
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 } }
});