Press ⌘K (or Ctrl+K) to search every page and blog post. Built on Radix Dialog + Fuse.js with zero runtime overhead.
Velocity X ships a keyboard-triggered search palette. Press ⌘K (macOS) or Ctrl+K (Windows/Linux), and a dialog slides in. Type a few letters, and the site fuzzes across all published pages and blog posts. Hit Enter, and you're there. No page load, no navigation delay — Fuse.js does the matching client-side in under 50ms.
The inspiration is obvious: Vercel's command palette, Linear's search, Arc's quick launcher. Developers expect it. Your content-heavy marketing site should have it too. Pre-indexing at build time means zero runtime cost, zero API calls, zero privacy concerns. Just fast.
Why ⌘K Matters for Content-Heavy Sites
A typical Velocity X site ships 40+ pages: product pages, case studies, 30+ blog posts, docs sections, pricing, team. A visitor looking for "authentication" or "Tailwind" shouldn't click through breadcrumbs and sidebars. They should press one key and jump directly to what they need. This is especially true for technical buyers (developers, CTOs, technical founders) who live in their keyboard and expect every site they visit to behave like their IDE.
Search also solves discovery. A reader lands on your blog for one post, then presses ⌘K out of habit and finds three other posts they didn't know existed. Engagement up. Bounce rate down. The pattern compounds: every keystroke is a micro-commitment to stay on your site longer.
The Architecture
Velocity X builds a static JSON index at compile time. The Astro config has a plugin that walks every page, extracts title + description + URL, and writes a `.json` file to public. When the site loads, React injects a `
The dialog is Radix `
` — accessible out of the box, respects `Escape`, doesn't fight with `ViewTransitions`. Fuse.js does the fuzzy matching: "cmd K" finds "⌘K Search Palette", "auth" finds "Authentication in Next.js", typos are forgiven. The index lives in memory; search is synchronous; no network calls.Here's the data shape: `[{ title, description, url, category }, ...]`. Category is inferred from the file path — `/blog/post-title` → "Blog", `/pricing` → "Pricing", `/services/design` → "Services". A keyboard shortcut listener on window mounts the dialog. Pressing ⌘K or Ctrl+K triggers it, regardless of focus. Arrow keys navigate results. Enter commits. Simple.
Keyboard Shortcuts
⌘K / Ctrl+K: Open the palette. Arrow Up/Down: Navigate results. Enter: Navigate to selected result. Escape: Close. Type: Filter in real time. The shortcut works from anywhere on the page — button, input, textarea — no conflicts. If you're editing a form and press ⌘K, it opens the palette, not a keyboard input. This requires capturing keydown at the window level before it bubbles.
Mobile gets a search icon in the header. Tap it, get a modal with a native `` and the same result list. No ⌘K on touch devices — it's a desktop gesture.
Build-Time Indexing
The index is built once during the Astro build, not on every page load. The build step scans `src/pages/**` and `src/content/**`, reads frontmatter or Astro component export comments, and generates `public/search-index.json`. This means the index is always in sync with deployed content — no stale entries, no missed posts. Redeploy, index regenerates.
File size matters. If your index is 500kb, it loads slower on mobile. Velocity X caps the index at 100 entries (50 pages, 50 posts) and paginates results into "more results" if needed. For larger sites, split the index or add a server-side fallback. For most marketing sites, the full index loads in under 100ms on 4G.
Why Fuse.js
Fuse.js is 14kb gzipped, fast, and does fuzzy matching out of the box. It's not a full-text search engine — no stemming, no language detection, no AI reranking. But for a marketing site, you don't need it. "Design system" matches "design-system-components" and "system design". Typos are forgiven. Exact matches rank higher. That's enough.
Alternatives exist: Lunr.js (22kb), MiniSearch (13kb), even a hand-rolled matcher. Fuse.js sits in the sweet spot of size and UX.
Frequently Asked
Does the search palette hurt SEO?
No. The index is a static JSON file, not hidden content. Every page and post is still crawlable and linkable from the nav. The search palette is a UX enhancement on top.
What if I have 1000+ pages?
The index grows linearly. At 1000 pages, the JSON is maybe 500kb–1mb depending on metadata verbosity. It's still faster to ship a large static index than to hit a server. If you're worried, paginate results or add a server-side fallback for large sites.
Can I search blog post content, not just titles?
Yes, but it's slower. Indexing full post bodies means a 5–10mb JSON file. For a marketing site, title + description search is usually sufficient. If you need body search, ship Fuse.js with the body content included and accept the payload cost.
Does it work offline?
Yes, as long as the index is cached by the browser. Service workers can cache the JSON. Subsequent visits search instantly, even without connectivity. For real offline support, combine with a service worker strategy.
How do I exclude pages from the index?
Add a frontmatter flag: `search: false`. The build step skips those pages. Useful for noindex pages, drafts, or dashboard screens.
Can I rerank results or add custom scoring?
Yes. Fuse.js takes a `keys` array to weight fields — title higher than description. You can also pre-process results after Fuse returns them, sorting by publish date, popularity, or custom metadata. The pattern scales from simple fuzzy matching to complex ranking logic.
The Bottom Line
⌘K search is table stakes for developer-forward marketing sites. It signals confidence in your content — you're not hiding behind SEO, you're surfacing everything upfront. Build the index at compile time, ship Fuse.js, bind a keyboard listener, and watch your users skip the nav. Velocity X pricing includes search palettes. See how transparent pricing feeds qualified search, or press ⌘K right now and try it yourself.