Search
Client-side full-text search powered by Orama with keyboard navigation, typo tolerance, and section-level results.
Grafos includes full-text client-side search powered by Orama. Search works entirely in the browser with no server-side component, making it fast and available even on statically exported sites.
How It Works
The search system has two phases:
Build time. During the pre-build step, Grafos reads all markdown files, extracts structured data (headings and their associated content blocks), and builds a search index. This index is written to .grafos/search-index.json and served as a static asset from public/_grafos/search-index.json.
Runtime. When a user opens the search dialog (via Cmd+K or the search button), the browser fetches the pre-built index and creates an Orama database in memory. Results are ranked by relevance with boosted title matches, typo tolerance, and prefix matching.
Because the index is pre-built, search queries are instantaneous after the initial load. There is no network request per query, no server to call, and no loading state beyond the first interaction.
The search index is proportional to your content size. For a site with 100 pages, the index is typically 50-150KB gzipped. The search dialog and Orama engine are lazy-loaded on first interaction, so they do not affect initial page load.
Structured Search Index
Unlike flat per-page indexing, Grafos builds a structured search index where each page is expanded into multiple searchable entries:
- Page entry — the page title and description (boosted 3x)
- Heading entries — each heading becomes its own searchable document, with a URL fragment (
#heading-id) so results link directly to that section - Content entries — each paragraph and blockquote is indexed under its nearest heading
This means searching for a term that appears in a specific section links you directly to that section, not just the top of the page.
The structured data is extracted at build time by a remark plugin (remark-structure) that walks the markdown AST, associates content blocks with their nearest heading, and generates IDs using the same GitHub Slugger algorithm as the rendering pipeline.
Search Index Fields
Each entry in the search index has the following fields:
- title — the page title, heading text, or content block text
- description — the page's frontmatter description (page entries only)
- url — the full URL including fragment for heading/content entries
- type —
page,heading, ortext - pageSlug — the page this entry belongs to (used for grouping results)
- tags — frontmatter tags from the parent page
Pages with draft: true in their frontmatter are excluded from the index.
The index is rebuilt every time the pre-build step runs. In development, this happens when you start the dev server. In production, it runs once during pnpm build.
Search UI
The search interface is a modal dialog that appears over the current page. It supports:
- Keyboard shortcut —
Cmd+K(macOS) orCtrl+K(Windows/Linux) opens the dialog from any page - Real-time results — results update as you type with no debounce delay
- Keyboard navigation — arrow keys to move through results,
Enterto navigate,Escapeto close - Grouped results — results from the same page are grouped together, with the page result first followed by matching sections
- Type indicators — page results show the title, heading results show a hash icon, content results are indented with a vertical line
- Highlighted matches — matched terms are highlighted in the result titles
- Typo tolerance — Orama's built-in tolerance handles misspellings
- Animated height — the dialog resizes smoothly as results appear and change
The search dialog is lazy-loaded via next/dynamic and only fetched when first opened. The SearchProvider component wraps the docs layout, registers the global hotkey, and renders the dialog when active.
Customization
You can control search behavior in configuration:
features: {
search: true, // Set to false to disable search entirely
}
When search is disabled, the search index is not generated during the pre-build step, the Cmd+K shortcut is not registered, and the search button is hidden from the UI.
The Cmd+K shortcut works from any page in the docs, including tag pages and individual content pages. It is the fastest way to navigate a large site.
- 3D Interactive Graph — force-directed graph visualization with local and global views, instanced rendering, and Web Worker physics. See graph for details. - Full-text Search — client-side search powered by MiniSearch with a Cmd+K keyboard shortcut and fuzzy matching. See search for details. - Wikilinks — Obsidian-compatible link syntax including page links, aliased links, heading links, and block embeds. See wikilinks for details. - Backlinks — automatic bidirectional link tracking that shows which pages link to the current page. See backlinks for details. - Syntax Highlighting — code blocks highlighted by Shiki with support for dozens of languages and themes. - LaTeX Math — inline and block math expressions rendered by KaTeX through remark-math and rehype-katex. - Callouts — Obsidian-style callout blocks for tips, warnings, notes, and other admonitions. - Table of Contents — automatically generated from page headings with scroll-tracking and smooth navigation. - Dark Mode — dark by default with a toggle that persists in localStorage and renders flash-free via an inline head script. - On-demand OG Images — social sharing images generated at request time through a Next.js API route, not at build time. - Plugin System — extend the markdown pipeline, graph data, and page layout with reusable plugins. See plugins for details. - Tailwind CSS v4 Preset — theming through CSS custom properties with a custom Tailwind preset for consistent styling. See theme for details.
From here, explore the configuration guide to customize your site, or dive into the feature guides for the graph, search, and wikilinks systems.