Search solutions for static sites

Options for adding search to documentation, blogs and static-site gardens —
client-side (index shipped to the browser) vs server-side (self-hosted
engine). Researched 2026-09-13 for garden.h; resource numbers are indicative,
verify before committing. Elasticsearch excluded by policy (JVM, GB-class
RAM for what we need).

Requirement scope: English + Polish as targets. Czech/Slovak are tracked
only as reference implementations — how full West-Slavic language support
looks when adding Polish elsewhere. Shortlist under that scope:
Pagefind (EN porter2 + PL Snowball, zero backend) and ZBSearch
(Orama fork by the original team; EN built-in; PL via a custom stemmer —
see below) client-side; Meilisearch (explicit eng+pol locales,
typo tolerance) or Typesense (EN+PL stemming, ~30 MB idle) server-side.
lunr-languages works but downloads the whole index.

Client-side (no backend)

  • FlexSearch — what Quartz search uses. ~5 KB, index built at
    runtime from a JSON corpus. Fast prefix matching; no stemming, no
    diacritics folding. Fine ≤ a few hundred notes.
  • lunr + lunr-languages — classic SSG search (Hugo/MkDocs heritage).
    Full index downloaded upfront (bandwidth grows linearly — bad for large
    sites). Polish: ✅ (lunr-languages ships a PL stemmer + stopwords);
    Czech/Slovak: ❌.
  • Pagefind — Rust CLI indexes built HTML, ships chunked WASM index;
    searches load only the needed chunks (~100 KB payload for most sites,
    MDN-scale < 300 KB). Zero config, per-section results, filters.
    Multilingual via lang attribute; stemming follows Snowball →
    Polish: ✅ (added in the 1.x Snowball upgrade), Czech: ✅, Slovak: ❌.
    Best “just works at scale” option; no quartz-community plugin (see
    Quartz search).
  • Orama / ZBSearch (~2 KB; Orama formerly Lyra) — full-text + vector +
    hybrid in browser or server, BM25, typo tolerance. Prefers ZBSearch:
    fork by the original Orama team (ex co-founder/CTO Michele Riva + the
    whole engineering team) after the VC fallout — same engine, no business
    incentives, actively developed (v4.0.0). 32 languages with tokenizer +
    stopwords + stemmer: Czech ✅ (port of Lucene’s CzechStemmer, Dolamic &
    Savoy 2009 — suffix tables + palatalization, dependency-free), Slovak ✅
    (ZBSearch-only: hand-written light stemmer; explains why the Czech one is
    not a stand-in and how diacritic folding interacts with stemming — the
    blueprint for a Polish stemmer), Slovenian ✅, Polish ❌ in both.
    Gotcha: Orama’s sk code is Sanskrit, not Slovak (ZBSearch fixed
    this with svk). Custom stemmers are first-class:
    Stemmer = (word) => string passed to components.tokenizer; most
    built-ins are Snowball-compiled JS, so a Polish stemmer is
    snowball compiler → polish.sbl → JS plug-in. Strong middle-ground
    option for EN+PL.

Server-side (self-hosted engines)

  • Meilisearch — Rust, single binary, typo-tolerant, ~instant. Charabia
    tokenizer; locales setting (ISO-639-3) explicitly lists pol, ces AND
    slk
    — the only engine surveyed with explicit Slovak support. Hybrid
    keyword+vector search built in. Homelab-fit: container or binary,
    moderate RAM (~100–200 MB class).
  • Typesense — C++, single binary, ~30 MB idle, RAM-mapped, very fast.
    locale accepts any ISO 639-1 incl. pl/cs/sk → ICU normalization +
    collation; Snowball stemming → Czech ✅, Polish ✅ (Snowball PL exists),
    Slovak ❌ (no Snowball algorithm — gets normalization only). Dictionary
    stemming (stem_dictionary) as escape hatch.
  • Algolia — SaaS only (no self-host). Free 10k searches/mo, 50k records;
    OSS projects get 200k/mo with attribution. Excellent DX (InstantSearch
    UI), but data leaves your infrastructure — conflicts with the self-hosting
    premise. Reference point, not a candidate.
  • QMD — for the agent/wiki side (not website visitors): BM25 +
    vectors + LLM rerank in one local binary. Different problem, same family.

Language support matrix (PL / CS / SK)

EnginePolishCzechSlovak
Snowball (reference)
lunr-languages
Pagefind (Snowball)
Orama
Meilisearch locales
Typesense (ICU+Snowball)~ (norm. only)
FlexSearch (any lang)❌ no stemming at all

Czech is the best-represented West-Slavic language (official Snowball
algorithm + Orama full support) — for our EN+PL scope it serves purely as a
reference: the reference implementation pattern to copy when adding Polish
support anywhere. Slovak is covered only by Meilisearch.

Choosing

  • Small garden/blog (≤ ~500 pages): client-side. Pagefind if stems/multilingual
    matter; stock FlexSearch (Quartz search) if prefix search suffices.
  • Docs portal / work: Meilisearch (PL+CS+SK explicit, typo tolerance,
    hybrid) or Typesense (leanest RAM) behind the reverse proxy.
  • Inflected-Polish matching on a static site without a server: pre-stem the
    corpus at build time — see Polish stemming and lemmatization.

Cross-refs: Quartz search, Polish stemming and lemmatization,
QMD, RAG, Local LM roadmap.