Skip to main content

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

parsers
ParserMap
required
An object mapping search param keys to their parser configurations.
options
object
Optional configuration object.

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.
options
LoaderFunctionOptions
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

1

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
})
2

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>
  )
}
3

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=1Cache { q: 'foo', page: 1 }
Request 2: ?q=bar&page=2Cache { 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

1

Always call parse() first

Call parse() in your page component before any nested components try to access the cache.
2

Use withDefault for required values

Avoid null checks by providing default values:
const searchParamsCache = createSearchParamsCache({
  page: parseAsInteger.withDefault(1),
  limit: parseAsInteger.withDefault(20)
})
3

Create a single cache per feature

Define one cache configuration per page or feature area and reuse it across all related components.
4

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

Build docs developers (and LLMs) love