Warning
This is an internal project, and is not intended for public use. No support or stability guarantees are provided.
The useSearch hook provides a powerful client-side search engine using Orama for documentation sites. It handles index creation, search queries, and result formatting with built-in support for stemming, grouping, faceting, and customizable result types.
useSearch creates an in-memory search index from your sitemap data and provides instant search results with fuzzy matching, boosting, and tolerance controls. It's designed to work seamlessly with documentation structures that include pages, sections, subsections, component parts, and exports.
import { useSearch } from '@mui/internal-docs-infra/useSearch';
function SearchComponent() {
const { results, search, isReady, defaultResults, buildResultUrl } = useSearch({
sitemap: () => import('./sitemap'),
maxDefaultResults: 10,
enableStemming: true,
});
const handleSearch = (query: string) => {
search(query);
};
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
{results.results.map((group) =>
group.items.map((result) => (
<a key={result.id} href={buildResultUrl(result)}>
{result.title}
</a>
)),
)}
</div>
);
}
Enable stemming to improve search quality by reducing words to their root form:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
enableStemming: true, // Default: true
});
When enabled, searches for "running" will also match "run", "runs", and "runner".
Control how tolerant the search is to typos:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
tolerance: 2, // Default: 1
});
Higher tolerance values allow more character differences between the query and results.
Configure how many results to return:
const { search, defaultResults } = useSearch({
sitemap: () => import('./sitemap'),
maxDefaultResults: 10, // Default: undefined (all pages)
limit: 20, // Default: 20
});
When maxDefaultResults is undefined, all pages are included in the default results (but not headings, exports, or parts).
When deploying internally, use showPrivatePages to include pages with audience: 'private' in both the search index and default results:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
showPrivatePages: process.env.SHOW_PRIVATE_PAGES === 'true', // Default: false
});
When showPrivatePages is false (the default), pages with audience: 'private' are excluded from the search index.
Include page categories in result groups for better organization:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
includeCategoryInGroup: true, // Groups become "Overview Pages" vs just "Pages"
});
Boost specific fields to prioritize certain result types. You can import defaultSearchBoost and extend it:
import { useSearch, defaultSearchBoost } from '@mui/internal-docs-infra/useSearch';
const { search } = useSearch({
sitemap: () => import('./sitemap'),
boost: {
...defaultSearchBoost,
title: 3, // Override specific values
},
});
The default boost values are:
export const defaultSearchBoost = {
type: 100,
group: 100,
slug: 2,
path: 2,
title: 2,
page: 6,
pageKeywords: 15,
description: 1.5,
part: 1.5,
export: 1.3,
sectionTitle: 50,
section: 3,
subsection: 2.5,
props: 1.5,
dataAttributes: 1.5,
cssVariables: 1.5,
sections: 0.7,
subsections: 0.3,
keywords: 1.5,
};
Override how pages are converted to search entries:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
flattenPage: (page, sectionData) => {
return [
{
type: 'page',
title: page.title,
slug: page.slug,
path: page.path,
description: page.description,
sectionTitle: sectionData.title,
prefix: sectionData.prefix,
},
];
},
});
Override how search hits are formatted:
const { search } = useSearch({
sitemap: () => import('./sitemap'),
formatResult: (hit) => {
return {
type: 'page',
id: hit.id,
title: hit.document.title.toUpperCase(),
description: hit.document.description,
slug: hit.document.slug,
path: hit.document.path,
sectionTitle: hit.document.sectionTitle,
prefix: hit.document.prefix,
score: hit.score,
};
},
});
The buildResultUrl function handles different result types:
#section-slug to the page path#subsection-slug to the page path#part-name to the page path#export-name or #api-reference to the page pathExample output:
// Page result
buildResultUrl(pageResult); // "/components/button"
// Section result
buildResultUrl(sectionResult); // "/components/button#usage"
// Export result
buildResultUrl(exportResult); // "/components/button#api-reference"
The search index is built asynchronously when the component mounts. Use the isReady flag to show loading states:
const { isReady, search, results } = useSearch({
sitemap: () => import('./sitemap'),
});
if (!isReady) {
return <div>Loading search index...</div>;
}
Index creation is fast but happens only once per mount. Consider memoizing the sitemap import for optimal performance.
The useSearch hook requires a sitemap with page metadata. Here's how to set one up:
Use the transformMarkdownMetadata remark plugin to automatically extract titles, descriptions, and sections from your MDX pages:
// Each MDX page will have metadata extracted:
export const metadata = {
title: 'Button',
description: 'A clickable button component.',
keywords: ['button', 'click'],
};
Create a sitemap index file using createSitemap:
// app/sitemap/index.ts
import { createSitemap } from '@mui/internal-docs-infra/createSitemap';
import Components from '../components/page.mdx';
import Functions from '../functions/page.mdx';
export const sitemap = createSitemap(import.meta.url, {
Components,
Functions,
});
Import the sitemap dynamically in your search component:
const { search, results } = useSearch({
sitemap: () => import('../sitemap'),
});
See createSitemap for the full API and transformMarkdownMetadata for metadata extraction options.
type, title, and slug to prioritize exact matchesbuildResultUrl to ensure consistent URL formatting across result typesisReady before allowing searches to prevent errorsHook for managing search functionality with Orama
| Parameter | Type | Description |
|---|---|---|
| options | | Configuration options for search behavior |
UseSearchResult<{ type: 'string'; group: 'string'; title: 'string'; description: 'string'; slug: 'string'; sectionTitle: 'string'; prefix: 'string'; path: 'string'; keywords: 'string'; page: 'string'; pageKeywords: 'string'; sections: 'string'; subsections: 'string'; part: 'string'; export: 'string'; types: 'string'; props: 'string'; dataAttributes: 'string'; cssVariables: 'string'; section: 'string'; subsection: 'string' }>type defaultSearchBoost = {
type: 100;
group: 100;
slug: 2;
path: 2;
title: 2;
page: 10;
pageKeywords: 15;
description: 1.5;
part: 1.5;
export: 1.3;
types: 2;
sectionTitle: 50;
section: 3;
subsection: 2.5;
props: 1.5;
dataAttributes: 1.5;
cssVariables: 1.5;
sections: 0.7;
subsections: 0.3;
keywords: 1.5;
}createSitemap - Define sitemap data for search indexingtransformMarkdownMetadata - Extract page metadata from MDXloadServerSitemap - Runtime sitemap loadingwithDocsInfra - Next.js plugin that configures the build