Skip to content

feat(nextra): add maxResults prop to Search component#4998

Open
assanee wants to merge 1 commit into
shuding:mainfrom
assanee:feat/search-max-results
Open

feat(nextra): add maxResults prop to Search component#4998
assanee wants to merge 1 commit into
shuding:mainfrom
assanee:feat/search-max-results

Conversation

@assanee
Copy link
Copy Markdown

@assanee assanee commented May 12, 2026

Problem

<Search> (packages/nextra/src/client/components/search.tsx) currently fetches the fragment JSON for every result Pagefind returns, then renders every one as a DOM node with dangerouslySetInnerHTML:

const response = await window.pagefind.debouncedSearch(value, searchOptions)
const data = await Promise.all(response.results.map(o => o.data()))
setResults(data.map(...))

On large docs sites a short query can match hundreds of pages. The browser then:

  • fetches hundreds of fragment JSON files in parallel
  • runs .data() for each one
  • renders hundreds of <Result> components on the main thread

In practice this blocks the main thread for 15+ seconds and Chrome shows the "Page Unresponsive" dialog.

The dropdown itself only shows ~10 results at a time, so 99% of that work is hydrating fragments the user will never see.

Reproduction

Docs site with several hundred indexed pages. Typing any 1–2 character query (very common at the start of typing) returns hundreds of matches, and the search dropdown hangs the tab.

Solution

Add an optional maxResults prop to <Search> (default 12) and slice response.results before calling .data() on each:

const data = await Promise.all(
  response.results.slice(0, maxResults).map(o => o.data())
)

This caps the per-query work (fragment fetches + result DOM nodes) at a sensible default while letting consumers opt back into the previous behavior by passing a higher value (e.g. <Search maxResults={Infinity} />).

Pagefind already returns results in relevance order, so slicing the top N is the right cut — and matches what users actually scan in the dropdown anyway.

Cap how many Pagefind results are hydrated via .data() and rendered.
On large docs sites a short query can match hundreds of pages, and the
previous code fetched fragment JSON for every result and rendered each
on the main thread — long enough to block the tab and trigger Chrome's
"Page Unresponsive" dialog. The dropdown only shows ~10 results at a
time, so most of that work was never visible to the user.

Default: 12. Backwards compatible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant