cmdk-static-fulltext-search
CMDK Static Full-Text Search Pattern
This pattern implements a global Cmd+K command menu that can search across massive amounts of Markdown/MDX content instantly without paying for a database or serverless execution costs.
When to Use
- When a project requires a global search feature.
- When the content is file-system based (MDX, Markdown) or fetched from a headless CMS at build time.
- When you want to avoid third-party search tools like Algolia.
- When you need full-text search (searching the raw body text) rather than just title/URL matching.
Implementation Details
1. Static API Route (src/app/api/search/route.ts)
Instead of fetching data on every keypress or paying for serverless execution, we force Next.js to bake our entire content payload into a static JSON edge-cached file at build time.
import { NextResponse } from "next/server";
// Import your data fetchers/local FS queries here
// CRITICAL: Force static generation to avoid $Vercel serverless execution fees.
export const dynamic = "force-static";
export async function GET() {
// 1. Fetch all posts/MDX files
// 2. Map them into `{ title, href, type, content: rawMdxString }`
// 3. Return as a single `NextResponse.json()`
}
2. High-Performance CmdK Component (src/components/ui/SearchPalette.tsx)
Because we pass raw Markdown chunks (often thousands of characters long) into cmdk to be searched, cmdk's default command-score algorithm will block the main thread and freeze the browser on every keypress. We bypass this with a basic native .includes() search.
import { useState, useEffect, useCallback } from "react";
import { Command } from "cmdk";
export function SearchPalette() {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const [results, setResults] = useState([]);
// Fetch data only when they open the palette
useEffect(() => {
if (open && results.length === 0) {
// eslint-disable-next-line react-hooks/set-state-in-effect
fetch("/api/search")
.then((res) => res.json())
.then(setResults);
}
}, [open, results.length]);
return (
<div className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh] px-4">
{/* ... Add backdrop & Cmd K event listener ... */}
<Command
shouldFilter={true}
// CRITICAL: Fast native filter prevents main-thread blocking on giant text payloads
filter={(value, searchString) => {
if (value.toLowerCase().includes(searchString.toLowerCase()))
return 1;
return 0;
}}
>
<Command.Input value={search} onValueChange={setSearch} />
<Command.List>
{results.map((result) => (
<Command.Item
key={result.href}
// CRITICAL: Combine type, title, and raw content for the full-text matching string
value={`${result.type} ${result.title} ${result.content || ""}`}
onSelect={() => router.push(result.href)}
>
{/* Title Rendering */}
{result.title}
{/* Dynamic Snippet Generator: Call a method to extract +/- 40 characters from where `search` matched `result.content` */}
</Command.Item>
))}
</Command.List>
</Command>
</div>
);
}
3. Dynamic Snippet Generation
Extract the surrounding 80 characters of text where the match occurs, sanitize Markdown symbols, and highlight the query string within the snippet:
const getSnippet = (content?: string, query?: string) => {
if (!content || !query || query.length < 3) return null;
const cleanContent = content
.replace(/[#*\`_\[\]()]/g, " ")
.replace(/\s+/g, " ");
const index = cleanContent.toLowerCase().indexOf(query.toLowerCase());
if (index === -1) return null;
const start = Math.max(0, index - 40);
const end = Math.min(cleanContent.length, index + query.length + 40);
let snippet = cleanContent.slice(start, end);
if (start > 0) snippet = "..." + snippet;
if (end < cleanContent.length) snippet = snippet + "...";
const regex = new RegExp(`(${query})`, "gi");
const parts = snippet.split(regex);
return (
<span>
{parts.map((part, i) =>
part.toLowerCase() === query.toLowerCase() ? (
<span key={i} className="font-bold text-accent">
{part}
</span>
) : (
part
),
)}
</span>
);
};
More from baphomet480/claude-skills
kitchen-sink-design-system
Kitchen Sink design system workflow for Next.js and React projects, with secondary support for Astro, SvelteKit, Nuxt, and static HTML. Use when asked for a Kitchen Sink page, Design System, UI Audit, Style Guide, or Component Inventory, or when a project needs a component inventory plus component creation and a sink page implementation. Covers CVA variant architecture, Tailwind v3/v4 token systems, shadcn/ui integration, and TinaCMS content modeling.
40deep-research
Conduct comprehensive, multi-round research that produces rich visual reports. Use when asked for "deep research", "comprehensive analysis", "compare frameworks", "evaluate options", "research the state of X", or any task requiring investigation across 10+ sources. NOT for quick lookups — this is a 5-15 minute deep dive that produces a briefing-quality artifact with screenshots, diagrams, tables, and cited findings.
37design-lookup
Search and retrieve CSS components, SVG icons, design patterns, and visual inspiration from the web. Use when the user asks to find, look up, or search for CSS snippets, SVG icons, UI components, loading spinners, animations, design inspiration, or any visual/frontend design resource. Triggers on requests like "find me a CSS button", "look up an SVG spinner", "search for a card component", "find a wave divider SVG", or "get design inspiration for a dashboard".
34nextjs-tinacms
Build Next.js 16 + React 19 + TinaCMS sites with visual editing, blocks-based page builder, and complete SEO. Use this skill whenever the user mentions TinaCMS, Tina CMS, Next.js with a CMS, visual editing with Next.js, click-to-edit, content-managed Next.js site, blocks pattern page builder, or migrating to Next.js + TinaCMS. Also trigger for TinaCMS schema design, self-hosted TinaCMS, TinaCMS media configuration, or any TinaCMS troubleshooting. Covers Day 0-2 setup from scaffolding through production deployment on Vercel.
32cloudflare-pages
Deploy static sites to Cloudflare Pages with custom domains and CI/CD. Use when the user wants to deploy a site to Cloudflare Pages, add a custom domain to a Pages project, set up GitHub Actions CI/CD for Cloudflare Pages, roll back a deployment, or verify deployment status. Triggers on "deploy to Cloudflare", "Cloudflare Pages", "add custom domain", "pages deploy", or any Cloudflare Pages hosting workflow.
31local-ocr
Local OCR pipeline for AI agents featuring auto-rotation, deskew, and searchable PDF generation via ocrmypdf and Tesseract.
23