How to implement CMD+K search module in Next.js with Sanity?

1 replyLast updated: Nov 30, 2025

Any suggestions on how i can implement search and press (cmd + k) to open a module i can search in with NextJS + Sanity?
AI Update

For implementing a cmd+k search modal in your Next.js + Sanity site, you'll want to combine a command palette UI library with Sanity's content querying. Here's my recommended approach:

UI Component

Use a dedicated command palette library like cmdk (by Paco Coursey) or kbar. Both are excellent React libraries specifically designed for cmd+k interfaces. cmdk is particularly lightweight and flexible.

Install it:

npm install cmdk

Search Implementation

For the actual search, you have a few options:

1. Client-side GROQ queries (simplest for getting started):

import {client} from '@/sanity/lib/client'

async function searchContent(query: string) {
  const results = await client.fetch(
    `*[_type in ["post", "page"] && [title, body] match $query] | order(_score desc) [0...10] {
      _id,
      _type,
      title,
      slug
    }`,
    {query: `${query}*`}
  )
  return results
}

2. Use score() and boost() functions in GROQ for more sophisticated relevance ranking:

*[_type == "post" && title match $query] 
| score(
    boost(title match $query, 3),
    boost(body match $query, 1)
  )
| order(_score desc)

3. Server Actions (recommended for production): Create a Next.js server action or API route to handle search queries server-side, which keeps your Sanity token secure and reduces client bundle size.

Basic Implementation Example

'use client'

import {Command} from 'cmdk'
import {useState, useEffect} from 'react'
import {useRouter} from 'next/navigation'

export function SearchModal() {
  const [open, setOpen] = useState(false)
  const [search, setSearch] = useState('')
  const [results, setResults] = useState([])
  const router = useRouter()

  // Keyboard shortcut
  useEffect(() => {
    const down = (e: KeyboardEvent) => {
      if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
        e.preventDefault()
        setOpen((open) => !open)
      }
    }
    document.addEventListener('keydown', down)
    return () => document.removeEventListener('keydown', down)
  }, [])

  // Debounced search
  useEffect(() => {
    if (!search) return
    const timer = setTimeout(async () => {
      const data = await fetch(`/api/search?q=${search}`).then(r => r.json())
      setResults(data)
    }, 300)
    return () => clearTimeout(timer)
  }, [search])

  return (
    <Command.Dialog open={open} onOpenChange={setOpen}>
      <Command.Input 
        value={search} 
        onValueChange={setSearch}
        placeholder="Search content..." 
      />
      <Command.List>
        {results.map((item) => (
          <Command.Item
            key={item._id}
            onSelect={() => {
              router.push(`/${item.slug.current}`)
              setOpen(false)
            }}
          >
            {item.title}
          </Command.Item>
        ))}
      </Command.List>
    </Command.Dialog>
  )
}

Performance Tips

The command palette pattern is great for content sites because it provides quick navigation without cluttering your UI. The combination of cmdk + GROQ queries gives you a powerful, flexible search that feels native to modern web apps.

Show original thread
1 reply

Was this answer helpful?

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Related contributions