Search indexes

Use a search index when your site provides its own browser search interface. Heine generates one locale-local JSON document from one or more collections, with checked membership, stable page facts, source-derived text, and URLs. Your site supplies the browser search engine and user interface.

This keeps search independent of one JavaScript library. A site can use a local script, while a larger one can replace that script with a suitable browser library without changing the generated document contract.

Create an index

Start with a configured collection, then add a named search index:

[collections.posts]
order = "published-desc"

[search.site]
collections = ["posts"]
path = "search/site.json"

collections must name one or more distinct configured collections. path must use the literal lowercase .json suffix. Heine unions members for each locale, removes duplicates, and orders the documents by page ID. The default locale receives search/site.<hash>.json; each other locale receives the same path below its locale prefix.

The filename includes the complete SHA-256 digest of the serialized JSON by default. A new index therefore has a new immutable URL when its contents change. Set versioned = false only when a stable endpoint is necessary and your deployment rechecks its cache.

Use the checked template view

Expose an index URL through search(), rather than constructing a path in a template:

{% set index = search(name="site") %}
<form data-search-url="{{ index.url }}">
  <input type="search" name="q">
</form>

The view contains name, count, base-path-aware url, and current-output-relative relative_url. search(name="site", locale="de") selects one exact configured locale. Unknown names and invalid locale values fail at the template call site.

Document contents

The current JSON envelope schema is format 3. Each document has stable metadata, including draft. It is false in an ordinary index and true only for a draft included through heine serve --drafts, so a site-owned preview interface can label it. Its sections array contains source-derived text from every declared Markdown input of its page. A section has text and a fragment: the fragment is null unless that text begins at a heading with an existing generated table-of-contents identifier. A browser can therefore link a matching section directly, while a page without such a heading remains linkable at its ordinary page URL.

Markdown text, headings, link labels, image alternatives, code, and recognized math source contribute. Authored raw HTML contributes semantic text nodes and img alternatives, but not markup, URLs, comments, attributes, or the contents of script, style, and template. This means an Arabic quotation inside trusted raw HTML remains searchable as authored Unicode text, even on an English page. Heine does not inspect rendered HTML, templates, copied assets, or an inferred content.main body. See the reference for the full JSON shape and extraction rules.

Each structural Markdown block and image alternative remains separate in the extracted text, including a nested list below a labeled list item. Inline Markdown and inline HTML keep the adjacency that the author wrote.

Browser behavior belongs to the site

The starter site includes a dependency-free browser example. It searches the generated index locally, creates its result DOM safely, and keeps the query and result page in the URL. It is a usable starting point, not a required ranking policy or a bundled Heine component.

Its search form may set data-context-before and data-context-after to the preferred Unicode code-point context before and after a complete match. The client uses 80 and 160 when those attributes are absent. The maintained example sites use those same values.

data-context-before-max and data-context-after-max bound an extension to a nearby word boundary. They default to 100 and 200, or to the corresponding preferred value when it is larger. When a preferred edge falls inside a word in a script with ordinary word boundaries, the client includes the whole word if its boundary lies within that maximum. Otherwise it cuts at the maximum. Scripts without ordinary word boundaries use the preferred edge directly. These limits bound context rather than matching text itself, so an unusually long query can make an excerpt exceed its context values.

The example matches each query term at the start of a word. It gives title matches priority, adds context from matching Markdown when it can, and falls back to a page description for a title-only match. Its exact ranking and excerpt choices are site policy, not part of Heine's search-index contract.

Search indexes are rebuilt by full builds and heine serve. --quick leaves the previously published index unchanged, because it cannot safely establish complete collection membership after every possible source change.