perf(website): cache normalized search fields (#4245)

Precompute lowercased search fields once per index identity so each query avoids repeating normalization across every indexed item.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Co-authored-by: Waseem <wmohammed@pros.com>
This commit is contained in:
Waseem Mohammed
2026-10-01 11:20:14 +10:00
committed by GitHub
co-authored by Waseem
parent 0c54bcee82
commit cc8c1ac4d8
+26 -7
View File
@@ -29,6 +29,12 @@ const MAX_PER_GROUP = 4;
const MAX_PAGEFIND_HITS = 12;
type ResultGroup = { category: SearchCategory; items: SearchItem[] };
type NormalizedSearchItem = {
item: SearchItem;
title: string;
description: string;
category: string;
};
/**
* Shared top-navigation search. A collapsed magnifier button expands into a
@@ -120,15 +126,28 @@ export function TopNavSearch({
};
}, [trimmed]);
const normalizedIndex = useMemo<NormalizedSearchItem[]>(
() =>
index.map((item) => ({
item,
title: item.title.toLowerCase(),
description: item.description.toLowerCase(),
category: item.category.toLowerCase(),
})),
[index],
);
const groups = useMemo<ResultGroup[]>(() => {
const query = trimmed.toLowerCase();
if (query.length === 0) return [];
const staticMatches = index.filter(
(item) =>
item.title.toLowerCase().includes(query) ||
item.description.toLowerCase().includes(query) ||
item.category.toLowerCase().includes(query),
);
const staticMatches = normalizedIndex
.filter(
({ title, description, category }) =>
title.includes(query) ||
description.includes(query) ||
category.includes(query),
)
.map(({ item }) => item);
// Static hits win on ties: they carry curated titles and descriptions.
const seen = new Set(staticMatches.map((item) => hrefKey(item.href)));
const matches = [...staticMatches];
@@ -150,7 +169,7 @@ export function TopNavSearch({
grouped.push({ category, items });
}
return grouped;
}, [trimmed, index, pagefindHits]);
}, [trimmed, normalizedIndex, pagefindHits]);
const flatResults = useMemo(
() => groups.flatMap((group) => group.items),