Seek.js

Architecture

How Seek connects a static Pagefind index to an answer endpoint you own

Architecture

Project status: v0, in development

This page describes the design being built against, not code that exists yet. It is published early so the architecture can be reviewed while it is still cheap to change.

Seek has one job: connect a static search index to a small serverless function you own, so a site can answer questions in prose with citations without anyone running a server or opening a vendor account.

Everything below follows from four decisions.

1. Retrieval is Pagefind

Seek does not build its own index format, and does not embed anything. Pagefind already solves the hard parts of static search:

  • Sharded index chunks. The browser fetches only the shards a query touches, so the payload stays around 300 kB even at 50,000 pages.
  • URL and anchor binding. Results link to the right page and the right heading.
  • Chrome removal. Navigation, sidebars, and footers do not pollute results.
  • Automatic multilingual indexing. Language is read from each page's <html lang> attribute, giving correct stemming across 40+ languages with no configuration.
  • One self-contained binary. Rust, with no Node requirement and no native addon to compile.

Reimplementing that work would add risk and maintenance without adding capability, so Seek builds on it. Pagefind is MIT-licensed and developed by CloudCannon; it is a dependency Seek credits rather than hides.

2. One input: a directory of built HTML

seek build ./dist takes a folder of HTML — nothing else. Not a framework plugin, not a config-file integration, not a crawler pointed at a live URL.

The consequence is that Seek is agnostic by construction. Hugo (Go), Jekyll (Ruby), Sphinx and MkDocs (Python), Astro, Next.js, or a handwritten index.html all produce the same input, so there is no per-framework adapter to maintain and no language runtime to match.

The build step runs no model, so it costs nothing and takes seconds. It is safe to run on every commit.

3. Reasoning happens at query time, not build time

An earlier design embedded every chunk at build time and ran vector search in the browser. That approach does not hold up: browser-side vector search needs the query embedded in the browser, which means either a 10–15 MB model download on first search or a network call per keystroke. The index size was also wrong — a 5,000-page site lands near 12 MB, not the 1.5 MB once advertised.

So semantics move to query time. The answer endpoint hands the model a search tool over the Pagefind index and lets it drive:

Because the model can look again with different words, thin first-pass results recover without any vector store. This is the semantic layer — retries instead of embeddings.

4. Citations cannot be hallucinated

The model never sees or writes a URL. It receives numbered sources [1]..[n] and is instructed to cite by number only; the client maps each [n] back to the URL that Pagefind returned for that source.

A fabricated link is therefore not something the model can produce — the worst it can do is cite the wrong number, and every rendered link points at a page that really exists. Citation drift is prevented by construction rather than measured after the fact.

Security: the browser never sends context

This is a hard requirement, not a default.

The API key lives only in the serverless function. The browser sends { question } and nothing else. Retrieval happens server-side, inside the function, against your own static index.

The earlier design had the browser retrieve locally and then POST the question plus the context to the endpoint. That is an open LLM relay: anyone can POST arbitrary text as "context" and have your function forward it to the model on your key. There is no way to validate attacker-supplied context, so the endpoint would spend the site owner's budget on someone else's prompts.

Sending only the question closes that hole, because the function controls what the model is allowed to read. Alongside it:

  • Answers are cached by normalized question, so repeats cost nothing and a scripted flood mostly hits cache.
  • Search itself never touches the endpoint, so ordinary usage — typing — cannot cost anything at all.
  • The endpoint is yours, so rate limiting, origin checks, and quotas are yours to set with the tools your host already gives you.

What Seek does not do

  • No hosted service and no proxy. There is no Seek server anywhere in the path.
  • No vector database, no embedding step, no index format of its own.
  • No build-time LLM calls.
  • No account, no approval process, no required branding.

See Packages for the pieces that implement this, and Comparison for how it compares to the alternatives.

On this page