LLM reference: Fetch llms.txt for the documentation index and latest API. Full docs in one file: llms-full.txt. Human-readable docs: nuqs.dev/docs.
Do not duplicate upstream API reference in this skill — use llms.txt / llms-full.txt for parsers, options, server cache, testing adapter, and troubleshooting.
When NOT to use nuqs (read first)
On TanStack Router or TanStack Start, prefer the router’s built-in search params unless you have a concrete reason to add nuqs.
| Situation | Use instead |
|---|---|
| App-owned filters, tabs, pagination, sort on a route | Route validateSearch (Zod) + Route.useSearch(), loaderDeps, typed <Link search={…}> |
Loaders / beforeLoad need query state |
Same parsers in validateSearch; search is typed in loader args |
| FMC TanStack Router / Start stack | Skill tanstack-router-conventions → references/params-search-ui-routes.md |
API routes under routes/api/* |
Parse request.url in GET with Zod — not route validateSearch (tanstack-start-conventions) |
Use nuqs on TanStack only when:
- A dependency or shared package already exposes
useQueryState/useQueryStates(integrate viacreateStandardSchemaV1— see nuqs TanStack adapter). - You are maintaining Next.js code that already uses nuqs.
Upstream caveat (2025): TanStack Router adapter is experimental and does not fully cover TanStack Start. For new Start apps, default to validateSearch, not NuqsAdapter.
When to use nuqs
- Next.js (App or Pages Router): primary fit —
NuqsAdapter+ client hooks + optionalnuqs/servercache for RSC. - Shared libraries consumed by Next.js apps that standardize on nuqs parsers/hooks.
- Tests of nuqs-powered components:
NuqsTestingAdapterfromnuqs/adapters/testing(see upstream Testing).
FMC conventions
TanStack Router / Start (preferred URL state):
- Colocate Zod search schemas with the route or feature; wire
validateSearchon the owning route file. - Wire
parseSearch/stringifySearchinrouter.tsx(pretty JSON baseline; jsurl only for large objects) —tanstack-router-conventions→router-search-serialization.md. - Start folder layout:
tanstack-start-conventions; avoidNuqsAdapterunless a subtree truly needs nuqs hooks.
Next.js + nuqs:
- Colocate parsers and hooks under the feature (e.g.
components/.../hooks/useQueryState/). - Place
NuqsAdapteronly oncomponents/layouts/Layout*.tsxshells that need URL search state — everything below is effectively a client boundary for nuqs. - Register search params in a central registry if redirects/normalization must preserve them.
State split: URL-shareable state → router search or nuqs; ephemeral UI → Zustand (zustand-state-management).
Next.js setup (summary)
Upstream detail: Installation · Adapters.
| Concern | Rule |
|---|---|
| Minimum Next.js (App Router) | 14.2.0+ (v2 dropped older 14.x shallow-routing hacks) |
| Provider | NuqsAdapter from nuqs/adapters/next/app or .../next/pages |
| Client hooks | import { useQueryState, … } from 'nuqs' in 'use client' components |
| Server / RSC | import { createSearchParamsCache, … } from 'nuqs/server' — never nuqs in Server Components |
| Shared parsers | Define once; import parsers from nuqs/server on server, nuqs on client |
| RSC refresh on URL change | shallow: false (v2: startTransition: true does not imply shallow: false) |
Next.js 15+ searchParams |
searchParams is a Promise — await before createSearchParamsCache.parse() |
Adapters (v2+): next/app, next/pages, next (unified), react, remix, react-router/v6, react-router/v7, tanstack-router (experimental), testing.
TanStack Router + nuqs (interop only)
When you must run nuqs hooks alongside the router:
import { createFileRoute, Link } from '@tanstack/react-router'
import { createStandardSchemaV1, parseAsIndex, parseAsString, useQueryStates } from 'nuqs'
const searchParams = {
searchQuery: parseAsString.withDefault(''),
pageIndex: parseAsIndex.withDefault(0),
}
export const Route = createFileRoute('/search')({
validateSearch: createStandardSchemaV1(searchParams, { partialOutput: true }),
component: RouteComponent,
})
function RouteComponent() {
const [{ searchQuery, pageIndex }] = useQueryStates(searchParams)
return <Link to="/search" search={{ searchQuery: 'foo' }} />
}
Limits: Only trivial parser types for type-safe <Link search>; urlKeys not supported with TanStack integration. Prefer native validateSearch for app code.
Root layout (only if needed): NuqsAdapter from nuqs/adapters/tanstack-router wrapping <Outlet /> in __root.tsx.
High-impact gotchas (not in every tutorial)
These are easy to miss; full behavior is in upstream docs.
- Typed parsers — URL values are strings; use
parseAsInteger,parseAsBoolean, etc., or you get string math and hydration issues. withDefault— Prefer over nullable state when a param should always have a value in UI.useQueryStates— Batch related params (filters, lat/lng/zoom) to avoid multiple history entries.nullsetter — Removes param from URL; pair withvalue={q ?? ''}for controlled inputs.clearOnDefault— Defaulttruekeeps URLs short; setfalseonly if the param must stay visible at default.createSearchParamsCache— Callparse(searchParams)once at the page, thenget()in child Server Components.parseAsJson— Requires a runtime validator in v2+ (type inference).- Debug —
localStorage.debug = 'nuqs'(v2 droppednext-usequerystatesubstring). - Package — ESM-only; import server utilities from
nuqs/server, notnuqs/parsers(removed in v2).
Related FMC skills
| Skill | Role |
|---|---|
tanstack-router-conventions |
validateSearch, pretty search URLs, loaders + Query |
tanstack-start-conventions |
Start layout, SSR, server functions, API vs UI search |
react-dev |
React TS patterns; Router TS notes point at router skill |