āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/47ng/nuqs/server-side ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
To parse search params server-side, you can use a loader function.
You create one using the createLoader{:ts} function, by passing it your search params
descriptor object:
// [!code word:createLoader]
import { parseAsFloat, createLoader } from 'nuqs/server'
// Describe your search params, and reuse this in useQueryStates / createSerializer:
export const coordinatesSearchParams = {
latitude: parseAsFloat.withDefault(0),
longitude: parseAsFloat.withDefault(0)
}
export const loadSearchParams = createLoader(coordinatesSearchParams)
Here, loadSearchParams{:ts} is a function that parses search params and returns
state variables to be consumed server-side (the same state type that useQueryStates{:ts} returns).
<Tabs items={["Next.js (app router)", "Next.js (pages router)", "API routes", "Remix / React Router", "React / client-side"]}>
// [!code word:loadSearchParams]
import { loadSearchParams } from './search-params'
import type { SearchParams } from 'nuqs/server'
type PageProps = {
searchParams: Promise<SearchParams>
}
export default async function Page({ searchParams }: PageProps) {
const { latitude, longitude } = await loadSearchParams(searchParams)
return <Map
lat={latitude}
lng={longitude}
/>
// Pro tip: you don't *have* to await the result.
// Pass the Promise object to children components wrapped in <Suspense>
// to benefit from PPR / dynamicIO and serve a static outer shell
// immediately, while streaming in the dynamic parts that depend on
// the search params when they become available.
}
// [!code word:loadSearchParams]
import type { GetServerSidePropsContext } from 'next'
export async function getServerSideProps({ query }: GetServerSidePropsContext) {
const { latitude, longitude } = loadSearchParams(query)
// Do some server-side calculations with the coordinates
return {
props: { ... }
}
}
// [!code word:loadSearchParams]
export function loader({ request }: LoaderFunctionArgs) {
const { latitude, longitude } = loadSearchParams(request) // request.url works too
// Do some server-side calculations with the coordinates
return ...
}
// Note: you can also use this client-side (or anywhere, really),
// for a one-off parsing of non-reactive search params:
loadSearchParams('https://example.com?latitude=42&longitude=12')
loadSearchParams(location.search)
loadSearchParams(new URL(...))
loadSearchParams(new URLSearchParams(...))
// App router, eg: app/api/location/route.ts
export async function GET(request: Request) {
const { latitude, longitude } = loadSearchParams(request)
// ...
}
// Pages router, eg: pages/api/location.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default function handler(
request: NextApiRequest,
response: NextApiResponse
) {
const { latitude, longitude } = loadSearchParams(request.query)
}
</Tabs>
<Callout type="warn" title="Note">
Loaders **don't validate** your data. If you expect positive integers
or JSON-encoded objects of a particular shape, you'll need to feed the result
of the loader to a schema validation library, like [Zod](https://zod.dev).
Built-in validation support is coming. Read the RFC. Alternatively, you can build validation into custom parsers. </Callout>
The loader function will accept the following input types to parse search params from:
https://example.com/?foo=bar?foo=bar (like location.search{:ts})URL{:ts} objectURLSearchParams{:ts} objectRequest{:ts} objectRecord<string, string | string[] | undefined>{:ts} (eg: { foo: 'bar' }{:ts})Promise{:ts} of any of the above, in which case it also returns a Promise.If a search param contains an invalid value for the associated parser (eg: ?count=banana for parseAsInteger{:ts}),
the default behaviour is to return the default value if specified, or null{:ts} otherwise.
You can turn on strict mode to instead throw an error on invalid values when running the loader:
const loadSearchParams = createLoader({
count: parseAsInteger.withDefault(0)
})
// Default: will return { count: 0 }
loadSearchParams('?count=banana')
// Strict mode: will throw an error
loadSearchParams('?count=banana', { strict: true })
// [nuqs] Error while parsing query `banana` for key `count`
<FeatureSupportMatrix introducedInVersion='1.13.0' support={{ supported: true, frameworks: ['Next.js (app router)']}} />
If you wish to access the searchParams in a deeply nested Server Component
(ie: not in the Page component), you can use createSearchParamsCache{:ts}
to do so in a type-safe manner.
Think of it as a loader combined with a way to propagate the parsed values down the RSC tree, like Context would on the client.
import {
createSearchParamsCache,
parseAsInteger,
parseAsString
} from 'nuqs/server'
// Note: import from 'nuqs/server' to avoid the "use client" directive
export const searchParamsCache = createSearchParamsCache({
// List your search param keys and associated parsers here:
q: parseAsString.withDefault(''),
maxResults: parseAsInteger.withDefault(10)
})
import { searchParamsCache } from './searchParams'
import { type SearchParams } from 'nuqs/server'
type PageProps = {
searchParams: Promise<SearchParams> // Next.js 15+: async searchParams prop
}
export default async function Page({ searchParams }: PageProps) {
// ā ļø Don't forget to call `parse` here.
// You can access type-safe values from the returned object:
const { q: query } = await searchParamsCache.parse(searchParams)
return (
<div>
<h1>Search Results for {query}</h1>
<Results />
</div>
)
}
function Results() {
// Access type-safe search params in children server components:
const maxResults = searchParamsCache.get('maxResults')
return <span>Showing up to {maxResults} results</span>
}
The cache will only be valid for the current page render
(see React's cache function).
Note: the cache only works for server components, but you may share your
parser declaration with useQueryStates for type-safety in client components:
import {
parseAsFloat,
createSearchParamsCache
} from 'nuqs/server'
export const coordinatesParsers = {
lat: parseAsFloat.withDefault(45.18),
lng: parseAsFloat.withDefault(5.72)
}
export const coordinatesCache = createSearchParamsCache(coordinatesParsers)
import { coordinatesCache } from './searchParams'
import { Server } from './server'
import { Client } from './client'
export default async function Page({ searchParams }) {
// Note: you can also use strict mode here:
await coordinatesCache.parse(searchParams, { strict: true })
return (
<>
<Server />
<Suspense>
<Client />
</Suspense>
</>
)
}
import { coordinatesCache } from './searchParams'
export function Server() {
const { lat, lng } = coordinatesCache.all()
// or access keys individually:
const lat = coordinatesCache.get('lat')
const lng = coordinatesCache.get('lng')
return (
<span>
Latitude: {lat} - Longitude: {lng}
</span>
)
}
'use client'
import { useQueryStates } from 'nuqs'
import { coordinatesParsers } from './searchParams'
export function Client() {
const [{ lat, lng }, setCoordinates] = useQueryStates(coordinatesParsers)
// ...
}
Just like with useQueryStates{:ts}, you can
define a urlKeys{:ts} object to map the variable names defined by the parser to
shorter keys in the URL. They will be translated on read and your codebase
can only refer to variable names that make sense for your domain or business logic.
export const coordinatesParsers = {
// Use human-readable variable names throughout your codebase
latitude: parseAsFloat.withDefault(45.18),
longitude: parseAsFloat.withDefault(5.72)
}
export const coordinatesCache = createSearchParamsCache(coordinatesParsers, {
urlKeys: {
// Remap them to read from shorter keys in the URL
latitude: 'lat',
longitude: 'lng'
}
})
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā