Next.js
The Quick Start works in the App Router. Two things are specific to Next.js: where the provider lives, and how commands come from your routes.
1. A client file for the palette
The provider, the palette and the shortcut use state and effects, so they run in a Client Component. Put them in one file that starts with 'use client', together with the provider config:
'use client'
import { useMemo, type ReactNode } from 'react'
import { useRouter } from 'next/navigation'
import { CommandEngineProvider, useCommandRegister } from 'cmdk-engine/react'
import { CommandPalette, useCommandPaletteShortcut } from 'cmdk-engine/adapters/cmdk'
import { sitemapToCommands } from 'cmdk-engine/adapters/sitemap'
import sitemap from '@/generated/command-routes.json'
const routeCommands = sitemapToCommands(sitemap)
function Palette() {
useCommandRegister(routeCommands)
useCommandPaletteShortcut() // Cmd+K / Ctrl+K
return <CommandPalette dialog placeholder="Search..." />
}
export function CommandMenu({ children }: { children: ReactNode }) {
const router = useRouter()
const config = useMemo(() => ({ onNavigate: (href: string) => router.push(href) }), [router])
return (
<CommandEngineProvider config={config}>
{children}
<Palette />
</CommandEngineProvider>
)
}Render it from the root layout, which stays a Server Component:
import type { ReactNode } from 'react'
import { CommandMenu } from './command-menu'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<CommandMenu>{children}</CommandMenu>
</body>
</html>
)
}Why the config stays in the client file
A Server Component can pass only serializable props to a Client Component. onNavigate, onSelect, t, asyncSources and access-control providers are functions, so a config written in a server layout fails the build ("Functions cannot be passed directly to Client Components"). Define the config in the 'use client' file: at module scope, or with useMemo when it needs a hook such as useRouter.
2. Commands from your routes
The CLI scans your app/ directory. Run it before each build:
{
"scripts": {
"prebuild": "cmdk-engine scan --framework nextjs-app"
}
}It writes src/generated/command-routes.json, which the @/ import above reads in a src/ layout; set output in cmdk-engine.config.ts to write it elsewhere. Every page file becomes a route:
- route groups and
@slotfolders add no segment; - private
_folders, intercepting(.)routes andapi/are skipped; [[...slug]]gives its parent's URL, and other dynamic routes are skipped unless you name them (below).
3. Apps under [locale]
When every page sits under a dynamic segment such as app/[locale], name it so the scan keeps those routes:
import { defineConfig } from 'cmdk-engine'
export default defineConfig({
framework: 'nextjs-app',
includeDynamic: ['locale'],
})app/[locale]/billing/page.tsx then becomes /:locale/billing. Fill the segment with the current locale when you register the commands:
function RouteCommands() {
const { locale } = useParams<{ locale: string }>()
const commands = useMemo(() => sitemapToCommands(sitemap, { params: { locale } }), [locale])
useCommandRegister(commands)
return null
}params values are inserted as given. Pass '' to drop the segment, for a default locale served without a prefix or a router that adds the prefix itself. Ids keep the placeholder (locale--billing), so frecency and Recent are shared across locales.
Pages Router
Scan with --framework nextjs-pages, render the provider in pages/_app.tsx, and navigate with useRouter from next/router. The Pages Router needs no 'use client'.
A runnable example
The Next.js App Router example has these files, with [locale] pages. Copy the folder, or open it in StackBlitz. More patterns are on the Examples page.