Overview
The createSearchParamsCache function creates a cache interface for accessing search parameters in deeply nested server components. It uses React’s cache function to ensure parsed values are available throughout the server component tree for a single request.
The cache only works in server components . For client components, use useQueryState or useQueryStates hooks.
Basic Usage
// searchParams.ts
import { createSearchParamsCache , parseAsString , parseAsInteger } from 'nuqs/server'
export const searchParamsCache = createSearchParamsCache ({
q: parseAsString . withDefault ( '' ),
page: parseAsInteger . withDefault ( 1 ),
category: parseAsString
})
// page.tsx
import { searchParamsCache } from './searchParams'
export default async function Page ({ searchParams }) {
// Parse and cache the search params
const { q } = await searchParamsCache . parse ( searchParams )
return (
< div >
< h1 > Search: { q } </ h1 >
< Results /> { /* Can access cached params */ }
</ div >
)
}
// Results.tsx (nested server component)
import { searchParamsCache } from './searchParams'
export function Results () {
// Access cached values without passing props
const page = searchParamsCache . get ( 'page' )
const category = searchParamsCache . get ( 'category' )
return < div > Page { page } - Category: { category || 'All' } </ div >
}
Function Signature
function createSearchParamsCache < Parsers extends ParserMap >(
parsers : Parsers ,
options ?: { urlKeys ?: UrlKeys < Parsers > }
) : CacheInterface < Parsers >
Parameters
An object mapping search param keys to their parser configurations.
Optional configuration object. Map internal state keys to different URL query parameter names. const searchParamsCache = createSearchParamsCache (
{ query: parseAsString },
{ urlKeys: { query: 'q' } }
)
Cache Interface Methods
The returned cache interface provides three methods:
parse()
Parses the incoming search params and stores them in the cache for the current request.
parse ( searchParams : SearchParams , options ?: LoaderFunctionOptions ): ParsedSearchParams
parse ( searchParams : Promise < SearchParams > , options ?: LoaderFunctionOptions ): Promise < ParsedSearchParams >
searchParams
SearchParams | Promise<SearchParams>
required
The searchParams prop from your page component. In Next.js 15+, this may be a Promise.
When true, throws an error if any parser fails. When false, returns null or the default value for failed parsers.
You must call parse() in your page component before accessing values with get() or all() in nested components.
get()
Retrieves a single cached search param value by key.
get < Key extends keyof Parsers >( key : Key ) : inferParserType < Parsers [ Key ]>
const page = searchParamsCache . get ( 'page' )
const query = searchParamsCache . get ( 'q' )
all()
Retrieves all cached search param values as an object.
all (): inferParserType < Parsers >
const { q , page , category } = searchParamsCache . all ()
Next.js 15 Support (Async searchParams)
Next.js 15 introduced a breaking change where the searchParams prop became a Promise. The cache handles both synchronous and asynchronous search params:
// Next.js 14 and earlier (synchronous)
export default function Page ({ searchParams }) {
const { q } = searchParamsCache . parse ( searchParams )
return < div > Search: { q } </ div >
}
// Next.js 15+ (asynchronous)
export default async function Page ({ searchParams }) {
const { q } = await searchParamsCache . parse ( searchParams )
return < div > Search: { q } </ div >
}
Complete Example
Define the cache
Create a shared cache configuration file: // app/search/searchParams.ts
import {
createSearchParamsCache ,
parseAsString ,
parseAsInteger ,
parseAsArrayOf
} from 'nuqs/server'
export const searchParamsCache = createSearchParamsCache ({
q: parseAsString . withDefault ( '' ),
page: parseAsInteger . withDefault ( 1 ),
limit: parseAsInteger . withDefault ( 20 ),
tags: parseAsArrayOf ( parseAsString ),
sortBy: parseAsString
})
Parse in the page component
Call parse() in your top-level page component: // app/search/page.tsx
import { searchParamsCache } from './searchParams'
import { SearchResults } from './SearchResults'
import { Pagination } from './Pagination'
export default async function SearchPage ({ searchParams }) {
// Parse and cache - must be called before nested components can access
const { q } = await searchParamsCache . parse ( searchParams )
return (
< div >
< h1 > Search Results for " { q } " </ h1 >
< SearchResults /> { /* Can access cache */ }
< Pagination /> { /* Can access cache */ }
</ div >
)
}
Access in nested components
Use get() or all() in any nested server component: // app/search/SearchResults.tsx
import { searchParamsCache } from './searchParams'
export async function SearchResults () {
// Access cached values without prop drilling
const { q , tags , sortBy } = searchParamsCache . all ()
const results = await searchDatabase ( q , tags , sortBy )
return (
< ul >
{ results . map ( result => (
< li key = { result . id } > { result . title } </ li >
)) }
</ ul >
)
}
// app/search/Pagination.tsx
import { searchParamsCache } from './searchParams'
export function Pagination () {
// Access individual values
const page = searchParamsCache . get ( 'page' )
const limit = searchParamsCache . get ( 'limit' )
return (
< div >
Showing page { page } ( { limit } items per page)
</ div >
)
}
Cache Lifecycle
The cache is scoped to a single page render using React’s cache function:
Created when the page component renders
Shared across all server components in the tree for that request
Cleared after the request completes
Isolated between different requests (no cross-request pollution)
// Each request gets its own cache instance
Request 1 : ? q = foo & page = 1 → Cache { q: 'foo' , page: 1 }
Request 2 : ? q = bar & page = 2 → Cache { q: 'bar' , page: 2 }
Strict Mode
Use strict mode to validate search params and throw errors for invalid values:
export default async function Page ({ searchParams }) {
try {
// Throws if any parser fails
const params = await searchParamsCache . parse ( searchParams , { strict: true })
} catch ( error ) {
// Handle invalid search params
console . error ( 'Invalid search params:' , error )
notFound ()
}
}
URL Keys Mapping
Map internal keys to different URL parameter names:
const searchParamsCache = createSearchParamsCache (
{
searchQuery: parseAsString ,
pageNumber: parseAsInteger . withDefault ( 1 )
},
{
urlKeys: {
searchQuery: 'q' ,
pageNumber: 'page'
}
}
)
// URL: ?q=laptop&page=2
// Access via internal names:
const searchQuery = searchParamsCache . get ( 'searchQuery' ) // "laptop"
const pageNumber = searchParamsCache . get ( 'pageNumber' ) // 2
Sharing with Client Components
You can share parser definitions between server and client code:
// searchParams.ts (shared parsers)
import { parseAsString , parseAsInteger } from 'nuqs/server'
export const searchParsers = {
q: parseAsString . withDefault ( '' ),
page: parseAsInteger . withDefault ( 1 )
}
export const searchParamsCache = createSearchParamsCache ( searchParsers )
// page.tsx (Server Component)
import { searchParamsCache } from './searchParams'
export default async function Page ({ searchParams }) {
await searchParamsCache . parse ( searchParams )
return (
<>
< ServerResults />
< ClientFilters />
</>
)
}
// ServerResults.tsx (Server Component)
import { searchParamsCache } from './searchParams'
export function ServerResults () {
const { q , page } = searchParamsCache . all ()
// Fetch and render results
}
// ClientFilters.tsx (Client Component)
'use client'
import { useQueryStates } from 'nuqs'
import { searchParsers } from './searchParams'
export function ClientFilters () {
const [ filters , setFilters ] = useQueryStates ( searchParsers )
// Render interactive filters
}
Error Handling
The cache will throw an error if you try to access values before calling parse():
// ❌ Will throw error
export default function Page ({ searchParams }) {
// parse() not called yet!
const page = searchParamsCache . get ( 'page' )
// Error: [nuqs] Cannot access search params before calling parse()
}
// ✅ Correct usage
export default async function Page ({ searchParams }) {
await searchParamsCache . parse ( searchParams )
const page = searchParamsCache . get ( 'page' ) // Works!
}
The cache will also throw if you call parse() multiple times with different inputs:
export default async function Page ({ searchParams }) {
await searchParamsCache . parse ( searchParams )
// ❌ Will throw error if searchParams changed
await searchParamsCache . parse ( differentSearchParams )
// Error: Cannot call parse() with different inputs in the same request
}
Type Inference
TypeScript infers the correct types based on your parsers:
const searchParamsCache = createSearchParamsCache ({
count: parseAsInteger , // number | null
active: parseAsBoolean . withDefault ( false ), // boolean
tags: parseAsArrayOf ( parseAsString ) // string[] | null
})
const count = searchParamsCache . get ( 'count' ) // number | null
const active = searchParamsCache . get ( 'active' ) // boolean
const tags = searchParamsCache . get ( 'tags' ) // string[] | null
const all = searchParamsCache . all ()
// {
// count: number | null,
// active: boolean,
// tags: string[] | null
// }
Best Practices
Always call parse() first
Call parse() in your page component before any nested components try to access the cache.
Use withDefault for required values
Avoid null checks by providing default values: const searchParamsCache = createSearchParamsCache ({
page: parseAsInteger . withDefault ( 1 ),
limit: parseAsInteger . withDefault ( 20 )
})
Create a single cache per feature
Define one cache configuration per page or feature area and reuse it across all related components.
Share parsers with client components
Export your parser configuration separately so both server cache and client hooks can use the same definitions.
Loaders Use createLoader for one-off parsing
Parsers Learn about available parser types