NextJS + Sanity - How to Implement Search and Press (Cmd + K) to Open a Module

1 replies
Last updated: Sep 9, 2022
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

  • Debounce your search queries (300-500ms is typical)
  • Consider caching popular searches
  • For large datasets, implement pagination or limit results to 10-20 items
  • Add loading states for better UX
  • Use GROQ's match operator with wildcards for flexible text matching

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.

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.

Was this answer helpful?