Front-Page Dual Search
Two search boxes on the home page — one searching everything, one searching everything except roadmap/** — is achievable, and both viable mechanisms were verified against the real built corpus. This is an alternative to (or a complement to) the ranking demotion analyzed in Options: rather than reordering one result list, it gives the reader two explicitly-scoped entry points.
Neither mechanism is a stock configuration option. No shipped Pagefind UI accepts a baseline “always apply this filter” option — inspecting pagefind-ui.js, pagefind-modular-ui.js, and pagefind-component-ui.js from Pagefind 1.5.2 shows the options each reads (pageSize, showImages, showEmptyFilters, processTerm, processResult, mergeIndex, sort, bundlePath), and none carries baseFilters or an equivalent. A first attempt passing filters: { section: { none: ['roadmap'] } } to PagefindUI was silently ignored — both boxes returned identical roadmap-led results. Anything built here has to route around that.
Route 1 — One Index, Section Filters, Custom Results UI
Section titled “Route 1 — One Index, Section Filters, Custom Results UI”Inject data-pagefind-filter="section:<top-level-segment>" into <main data-pagefind-body> at build time — the same post-build rewrite described as option A in Options — then query the Pagefind JS API directly with an exclusion filter.
await pagefind.search(term, { filters: { not: { section: 'roadmap' } } });Measured against the built corpus with all 1167 content pages tagged by section:
| Query | All sections | Excluding roadmap |
|---|---|---|
email | 233 | 101 |
settings | 619 | 222 |
bitemporal | 146 | 71 |
item image upload | 164 | 20 |
The filtered results are clean — email returns the system-email integration page, the shop-access domain model, and the email decision record at the top.
- For: One index, no duplicated content. The section tagging is reusable: the stock UI renders a section facet automatically when the index carries filters (verified — the header modal’s panel lists
current-system (61),roadmap (132),product (18), and so on for a search ofemail), so the same injection buys the facet described as option D for free. - Against: The JS API returns data, not markup. A second box built this way means owning a results component — rendering, excerpt highlighting, keyboard handling, accessibility — rather than reusing Starlight’s styled search. That is the whole cost of this route, and it is not small.
Route 2 — Two Indexes, Stock UI Twice
Section titled “Route 2 — Two Indexes, Stock UI Twice”Build a second Pagefind index over a roadmap-free copy of the build output, publish it at /pagefind-durable/, and mount a second stock PagefindUI pointed at it through the supported bundlePath option.
Verified end to end: two PagefindUI instances on one page, one with bundlePath: '/pagefind/' (1169 pages) and one with bundlePath: '/pagefind-durable/' (480 pages). Searching email returned 233 results led by roadmap pages in the first box, and 101 results led by current-system and decisions pages in the second — the same 101 the Route 1 filter produces, which cross-validates both mechanisms.
- For: No custom UI code. Both boxes are the same component Starlight already styles, so they inherit the site’s search appearance and behavior for free. The scoping is structural — a page absent from the index cannot leak into results.
- Against: A second index to build and ship. Indexing the 480-page durable subset took about the same few seconds as the full index, and its bundle is roughly 40% the size of the main one. The two indexes can drift if the exclusion copy and the site’s section layout diverge, so the exclusion rule needs to live next to the other path rules.
Variant — One Box With a Scope Selector
Section titled “Variant — One Box With a Scope Selector”A single search box with a scope control is achievable three ways, and the choice turns on one question: must the box default to a scope other than “everything”? No stock UI supports a preset filter, so a non-default scope forces a custom control.
Stock facet panel — zero custom code
Section titled “Stock facet panel — zero custom code”With section tags injected (Route 1), the stock UI’s own box already renders a scope control. Verified against the built corpus: searching email produced a panel listing current-system (61), roadmap (132), product (18), process (11), and every other section with live counts.
It is a checkbox list of all eleven sections rather than a two-choice switch, and it always opens unscoped. If “one box, reader picks scope” is the requirement and the default may be “everything”, this is free — it falls out of the same tagging.
This is implemented. The pagefind-section-filters integration in plugins/ derives each built page’s section from its path and writes data-pagefind-filter="section:<name>" into its <main data-pagefind-body> element before Starlight runs Pagefind. A production build tags 1168 pages — roadmap 690, current-system 167, process 102, product 99, about 43, domain 36, technology 11, decisions 10, vision 8, legal 1, home 1. Only 404.html and tools/qr-test-generator go untagged, and correctly so: neither carries a data-pagefind-body element.
Two smoke tests guard it — one driving the search modal to confirm the facet renders with counts, and one asserting that every page Starlight marks indexable is tagged. That second test earns its keep. An earlier version checked two known-good pages instead, and missed a real defect: the integration’s idempotence guard scanned the whole document for data-pagefind-filter, so a page whose prose merely mentioned the attribute read as already-tagged. The casualties were this page and Options — documenting the feature disabled it on the pages doing the documenting. Both automated reviewers on the pull request caught it. A page dropping out of the facet is silent, because search still works and simply cannot be scoped, so the invariant has to be asserted across the whole corpus rather than sampled.
One behavior to know about: the facet ships collapsed. Pagefind renders it as a Filters legend with a Section disclosure the reader must open, so scoping costs one extra click and is not visible until sought. Pagefind’s UI has an openFilters option that would expand it by default, but Starlight’s Zod schema strips any UI option it does not recognize, so using it requires overriding the Search component. That trade — one supported override against a visible-by-default scope control — is the natural follow-up if the collapsed facet proves too quiet.
Custom scope control over the JS API
Section titled “Custom scope control over the JS API”A segmented control — Everything / Excluding project roadmap — calling the API directly:
const scoped = { not: { section: 'roadmap' } };await pagefind.search(term, { filters: scope === 'durable' ? scoped : undefined });Full control over labels, over how many choices there are, and over which one is the default. Same cost as Route 1: the results list is ours to build.
Modular UI — partially verified
Section titled “Modular UI — partially verified”Pagefind ships a modular UI (pagefind-modular-ui.js, 14 KB) exporting Instance, Input, FilterPills, ResultList, and Summary, with triggerFilter(key, values) and triggerFilters(object) for programmatic scoping. FilterPills is a purpose-built scope selector.
The pills work. Mounted against the section-tagged index, they rendered All (233) | about (2) | current-system (61) | … | roadmap (132) for a search of email, and the counts tracked correctly as scope changed through triggerFilters.
The result list did not populate. Its entries stayed in placeholder skeleton state through a 4-second settle, with fragments served and no relevant 404s — the only failed requests were _assets files the HTML-only probe corpus does not carry. This may be a harness artifact or may be real: Pagefind 1.5.0 introduces a Component UI that it describes as replacing the Default UI, so the modular UI’s status is unclear. Do not choose this route without resolving that first — it would otherwise be the cheapest of the three, giving a stock scope selector and a stock result list with no custom rendering.
Recommendation
Section titled “Recommendation”Route 2 for the front page. The reason to put two boxes on the home page is to make scope explicit to a reader, and Route 2 delivers that with stock components and no UI surface to maintain. Route 1’s advantage — one index — does not pay for a hand-built results list.
Route 1’s tagging is still worth doing, independently, because the section facet in the header modal falls out of the same injection at no extra cost. The two routes are not exclusive: tag sections for the facet, and build the second index for the front page.
If the preference is one box with a scope selector rather than two boxes, the tagging alone delivers a working version with no custom code — the stock facet panel. That is the cheapest thing that answers the question, and it is worth living with before committing to a custom control. Move to a custom control only if the box must default to a scope other than “everything”, which no stock UI supports.
Caveats
Section titled “Caveats”- Pagefind does not run under
astro dev. The bundle only exists after a build, so both boxes need a graceful placeholder in the dev server rather than a console error. Starlight’s own search has the same constraint. - Base path.
bundlePathmust be derived fromimport.meta.env.BASE_URL, asSearch.astrodoes, or the boxes break under the/documentation/preview deploy. - Home page is a splash template.
src/content/docs/index.mdxusestemplate: splash, so the boxes go in as a component in MDX — the repository already has the React integration configured for islands.
Open Questions
Section titled “Open Questions”- Do the two boxes replace the ranking demotion, or complement it? If a reader picks scope explicitly on the home page, the header modal’s ranking may matter less — or may matter more, since it is the search used from every other page.
- What are the boxes labeled? “Everything” and “Excluding project roadmap” is accurate but wordy; the labels carry the whole affordance.
- Should the second box’s scope be “not roadmap” or an explicit allowlist of durable sections? An allowlist keeps a new top-level section out of the durable box until someone decides it belongs.
Copyright: © Arda Systems 2025-2026, All rights reserved