Skip to content

Options: Doc Search Ranking

Options for demoting roadmap/ pages in the documentation site’s search results, with the measurements that rule several of them in or out. Read Goal first for why this is wanted.

All measurements below were taken with the versions this repository’s package-lock.json pins — Starlight 0.38.5 and Pagefind 1.5.2 — using the probe harness in scratch/pagefind-probe/.

Starlight marks each page’s indexed region by putting data-pagefind-body on the <main> element (node_modules/@astrojs/starlight/components/Page.astro, line 47). Pagefind’s data-pagefind-weight attribute multiplies the ranking contribution of the text inside an element, over the range 0.0–10.0. Combining the two is the only per-page ranking control available, because Starlight’s pagefind configuration option is site-global: it exposes pageLength, termFrequency, termSaturation, and termSimilarity, plus indexWeight and mergeFilter which apply only to merged multisite indexes.

Starlight also honors pagefind: false in a page’s frontmatter, which drops the page from the index entirely.

Pagefind’s documentation does not state whether data-pagefind-weight cascades to nested elements, nor whether it applies on the data-pagefind-body element itself. Both were measured on five synthetic fixtures carrying identical text, varying only where the attribute sits, searched for "widget calibration".

FixtureMarkupScoreRelative
aNo weight (baseline)1.5221.00
dh1 outside a weighted <div weight="0.2">1.4930.98
bweight="0.2" on the data-pagefind-body element1.1070.73
cweight="0.2" on an inner div wrapping h1 and body1.1070.73
eweight="0.05" on the data-pagefind-body element0.9490.62

Two mechanism conclusions follow, and both hold regardless of corpus:

  1. The attribute works on the data-pagefind-body element itself and cascades into nested elements, including headings. Fixtures b and c score identically, so attributing the body element is equivalent to wrapping the content.
  2. The page’s h1 must be inside the weighted region. Fixture d moved the score by 2%. This rules out the otherwise-attractive route of wrapping content in the existing MarkdownContent.astro override, because Starlight renders PageTitle as a sibling of MarkdownContent, not a parent. The attribute has to land on <main>.

A weight sweep on the same fixtures (0, 0.05, 0.2, 0.5, 1, 2, 5, 10) is monotonic but compressed — even weight="0" scores 0.57 of baseline, and weight="10" only reaches 1.05. These fixtures are near-pure heading matches, which is the worst case for the attribute; the real corpus behaves very differently, which is why the numbers below are the ones that matter.

The production build’s HTML (1169 pages) was copied twice. In the treatment copy, data-pagefind-weight="0.2" was injected into <main data-pagefind-body> on all 689 roadmap/ pages and nothing else. Both copies were indexed with Pagefind 1.5.2 and queried identically.

QueryTotal hitsRoadmap in top 10First durable result
purchase order1084 → 41 → 1
universe pattern890 → 01 → 1
bitemporal1464 → 01 → 1
edit lifecycle1706 → 21 → 1
authentication2180 → 01 → 1
item image upload16410 → 10none → none
changelog21710 → 8none → 1
settings6196 → 53 → 1
email2339 → 15 → 2
scan1365 → 41 → 1

Roadmap pages in the top 10 fall from 54 to 34 out of 100 result slots. For purchase order, ranks 2 through 5 change from three roadmap pages and one product page to four durable pages (use cases, information model, current-system functional, decision record) while the roadmap pages slide to 6–9. For email, the first durable result moves from rank 5 to rank 2.

Two results deserve attention:

  • item image upload stays 10 out of 10 roadmap. The only content on that topic is roadmap content. Demotion correctly leaves it findable rather than manufacturing a durable result that does not exist.
  • changelog and settings improve only marginally. Both are terms where roadmap pages genuinely carry the densest coverage. A stronger weight would push them further; whether that is desirable is open question 2.

A small Astro integration listed before starlight() in the integrations array, hooking astro:build:done to add data-pagefind-weight to <main data-pagefind-body> in dist/roadmap/** before Pagefind indexes the output. Starlight runs Pagefind from its own astro:build:done hook (node_modules/@astrojs/starlight/index.ts, line 187) and Astro fires integration hooks in array order, so an earlier entry gets to mutate the built HTML first.

This is the option measured above, and it produced the result table.

  • For: No coupling to Starlight internals. The path rule sits in astro.config.mjs next to the existing starlight-llms-txt exclude: ['roadmap/**'], so both surfaces state their policy in one place. The weight is tunable without touching content.
  • Against: Depends on integration hook ordering — documented Astro behavior, but silent if it ever regresses, so it needs a test that asserts the attribute is present in the built output.

Vendor a copy of Starlight’s Page.astro and add the weight attribute conditionally.

  • For: Applies at render time, no build-order dependency.
  • Against: Page is an internal component Starlight discourages overriding; the copy must be re-reconciled on every Starlight upgrade. The repository already overrides MarkdownContent and Footer, both supported override points — this would be the first unsupported one.

C — Exclude roadmap from search entirely

Section titled “C — Exclude roadmap from search entirely”

Set pagefind: false on roadmap pages, or inject data-pagefind-ignore through the same rewrite as option A.

  • For: Exactly mirrors the policy already applied to the agent-facing surfaces. The only option offering a hard guarantee rather than a relevance multiplier.
  • Against: The item image upload row above is the argument against — that content becomes unreachable through search, and it is the only content on its topic. Setting frontmatter by hand would touch 689 files; path-based injection avoids that but is then no simpler than option A.

Inject data-pagefind-filter="section:roadmap" at the same point as option A. Pagefind’s default UI renders a filter panel automatically when the index carries filters, so this needs no UI override — verified against the built corpus in Front-Page Dual Search, where a search of email produced a facet listing every section with its hit count.

  • For: Gives readers an explicit, visible control. Composes with A rather than competing with it.
  • Against: Adds a facet panel to the search modal for all sections, a visible UI change beyond the stated scope.

Client-side reordering. Pagefind’s default UI exposes sort, which overrides relevance ranking entirely rather than adjusting it, and processResult, which can modify a result but cannot reorder the result set. Passing either would also require overriding Starlight’s Search component, because Starlight validates the pagefind option against a Zod schema that strips unrecognized keys.

Multisite indexWeight. Starlight surfaces Pagefind’s indexWeight, but it weights a whole merged index against another site’s index. With a single index it has no per-section meaning.

  1. Demote or exclude — should roadmap content still be findable through search while a project is active? The item image upload result argues for demotion.
  2. What weight? 0.2 produces the table above. Stronger values were not measured against the real corpus; the harness makes that a single re-run.
  3. Should the same rule cover other low-durability areas, or is roadmap/ the only one?
  4. Is the filter facet (option D) wanted, given it changes the search modal’s appearance?
  5. Should demotion vary by project status — a completed project’s pages demoted harder than an in-progress one’s?

The harness lives in scratch/pagefind-probe/ (untracked). It indexes a directory of HTML with the repository’s own Pagefind and drives the browser-only search API through Playwright.

Terminal window
# Mechanism fixtures
node build.mjs ./site && node probe.mjs "widget calibration" ./site
# Real corpus: baseline vs treatment, after `make build`
rsync -a --include='*/' --include='*.html' --exclude='*' ../../dist/ base/
rsync -a base/ treat/
find treat/roadmap -name '*.html' -print0 | xargs -0 \
perl -pi -e 's/<main data-pagefind-body/<main data-pagefind-body data-pagefind-weight="0.2"/'
node build.mjs ./base && node build.mjs ./treat && node compare.mjs ./base ./treat 10