Skip to content

feat: build the search index after page renders via templates.Defer (render-once B)#308

Merged
markdumay merged 2 commits into
mainfrom
perf/defer-index-build-rebased
Jul 16, 2026
Merged

feat: build the search index after page renders via templates.Defer (render-once B)#308
markdumay merged 2 commits into
mainfrom
perf/defer-index-build-rebased

Conversation

@markdumay

Copy link
Copy Markdown
Contributor

Render-once program — chunk B (HELD: merge after hinode#2042's runbook completes)

The parked perf/defer-index-build branch, rebased onto main (v5.0.3-era) and completed.
The index publishes per language from a templates.Defer block after all page renders;
the runtime always fetches (lazyLoad now only controls fetch timing).

Why now

The original park (2026-07-15) cited an absent wall win and a gate polluted by build
nondeterminism. Both causes are resolved by the render-once flip (gethinode/hinode#2042):

  • Measured on the flip base (M1 Pro, medians of 3 warm-stable runs): wall 10.92 → 7.32 s
    (−33%) at +1.6 s user CPU
    — the serial index build dominated the post-flip critical path.
  • Gate on the flip base: candidate double builds fully byte-identical; search index EN/FR/NL
    byte- and order-identical to stock; every candidate-vs-stock diff is the expected search-asset
    class or a correction of a stock defect (4 pages' broken sprite refs).

Placeholder-token fix (second commit)

A page demoing search="true" in content leaked an unsubstituted __hdeferred/…__d= token
into the published index (tokens substitute in HTML but not in resources.FromString assets).
Fixed by stripping the token in GetSearchDocs.html (before truncation); regression fixture +
check-script assertion included, verified both ways.

⚠️ Hazard documented for reviewers

Empirical Defer-token matrix: HTML → substituted; resources.FromString asset → raw leak (now
stripped); RSS/XML → Hugo panics (deferred execution with id … not found). hinode's own
rss.xml is safe (frontmatter descriptions), but a downstream site embedding a derived
.Summary of a search-demo page in a feed would crash — suggest a docs note and/or an
upstream Hugo issue.

Sequencing

Gate-clean ONLY on the flip base — on current main the pre-flip UID race still pollutes the
candidate. Merge order: hinode#2042 runbook first, then this.

🤖 Generated with Claude Code

markdumay and others added 2 commits July 16, 2026 16:06
The index used to be inlined into the core script bundle: flexsearch.index.js
called GetSearchDocs.html, whose .Plain walk over every page executed inside
the cached bundle partial while all page renders waited on its mutex.

The index documents are now published as a per-language JSON asset
(js/flexsearch-index.<lang>.json) from a templates.Defer block registered by
the search-input.html partial and the ModalSearch shortcode. The deferred
block runs after all pages have rendered, so .Plain reuses Hugo's cached
content instead of forcing serial renders. The runtime bundle fetches the
asset: immediately by default, or on the first search interaction when
lazyLoad is set. The searchindex output format is deprecated and now emits an
empty array; GetSearchIndex.html resolves the new asset URL.

Requires CSP connect-src 'self' in both modes; the module already declares it
in its csp block, so generated headers are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A page that demos a search UI inside its own content embeds the deferred
search-index publisher in .Content, leaving a raw templates.Defer
placeholder in the in-memory .Plain/.Summary read by GetSearchDocs.html —
Hugo only substitutes placeholders in published page output. Strip the
token from descriptions (before truncation can cut it in half) and from
content, so the published index stays clean.

Adds a regression fixture: a content page that embeds the search input
via a shortcode, plus a check-search-index.mjs assertion that no field of
any indexed document leaks a placeholder. The fixture pins an explicit
frontmatter summary: without it the derived .Summary carries the raw
placeholder into the RSS feed and Hugo panics with 'deferred execution
with id ... not found' while substituting placeholders in RSS output —
a hazard for sites whose feeds embed content demoing a search UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@markdumay markdumay merged commit 9b5b132 into main Jul 16, 2026
8 checks passed
@markdumay markdumay deleted the perf/defer-index-build-rebased branch July 16, 2026 17:49
@markdumay

Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 5.1.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant