On this page ▾
Search
Cmd+K (or Ctrl+K on Windows / Linux) opens the command palette. The same shortcut toggles it closed. Esc also closes. Hover a row to highlight it, click to open. Empty state shows your most recent visits, restored from localStorage. Keyboard navigation (arrow + Enter) is intentionally not wired yet; planned for a future polish pass.
How the index is built
A vite plugin walks src/content/**/*.md at build time and emits a list of records under the virtual module virtual:ngmd/search-index. Each markdown page produces three flavours of record:
- page record. One per file. Title-only — matches the page when the query lands in its title. Body matches surface through snippet records below so short queries don't fuzzy-match unrelated pages.
- section record. One per heading (
##or deeper). Anchored at the heading's slug so clicking jumps straight to it. - snippet record. Paragraph-sized chunks of the body (~280 chars, sentence-aware). Each snippet carries its enclosing heading's anchor so a click lands you in the right section, not at the page top.
The plugin runs on every dev-server .md change through Vite's file watcher (watchChange), including added and deleted pages, so authoring a page reflects in the palette immediately.
Opting a page out
Add noIndex: true to the frontmatter and the plugin skips the file entirely. Useful for stub pages, scaffold-only content, or 404 fallbacks.
---
title: Internal stub
noIndex: true
---
Two backends, one interface
The palette UI consumes a small SearchProvider interface (src/types/search.ts). Two implementations satisfy it:
3×, page titles 2×, body 1×.
site.algolia in ngmd.config.ts to switch.
The SearchService (src/app/services/search/search.service.ts) picks at runtime based on whether all three Algolia keys are present. No flag, no rebuild required.
Switching to Algolia
When the application is approved, Algolia sends you three values: appId, apiKey, and indexName. The apiKey is the search-only key, safe to commit.
pnpm add algoliasearch
site: {
// ...existing site fields
algolia: {
appId: 'YOUR_APP_ID',
apiKey: 'YOUR_SEARCH_KEY',
indexName: 'YOUR_INDEX',
},
},
The palette swaps backends on the next page load. Match highlighting works the same way (Algolia returns <mark>...</mark> directly; Orama wraps matches with the same tag client-side), so the visual output is identical.
Search history
Every successful navigation from the palette is appended to a localStorage-backed history (ngmd-search-history-v1). The empty-query state renders it newest-first with a Clear action. Star a recent entry to pin it as a favorite: favorites render above recents and are never trimmed, while recents are capped at ten entries.