nuqs Adapter
Derive nuqs parsers from a route's search codecs.
nuqs is the standard tool for writing search-param
state from client components — useQueryState(s) with batched updates,
history control, and shallow routing. What it asks of you is a parser per
key. @paramour-js/nuqs derives those parsers from the codecs your route
already declares, so URL state is never defined twice.
npm install @paramour-js/nuqs nuqsA filter panel from a route
nuqsParsers(route) turns a route's whole search config into an ordinary
nuqs parser map:
// @filename: app/products/route.def.ts
import { , } from "paramour";
export const = ("/products", {
: {
: .().(1),
: .().(),
: .(),
},
});
// @filename: app/products/filter-panel.tsx
"use client";
import { } from "@paramour-js/nuqs";
import { } from "nuqs";
import { } from "./route.def";
export function () {
const [{ , , }, ] = (
(),
);
return (
<>
<
={() => void ({ : 1, : .. })}
={ ?? ""}
/>
< ={() => void ({ : + 1 })}>
next page ({.} tags active)
</>
</>
);
}Everything the codecs declared carries over: page reads non-nullable
(its .default(1) became withDefault), q reads string | null (nuqs's
spelling of absent), tags is a repeated-key multi-parser that reads []
when absent, and every value round-trips through the same
wire format your server parse expects. When
the route's config changes, this component's types change with it.
The output is ordinary nuqs currency — withOptions, createSerializer,
createLoader, and the server cache all compose untouched. The adapter
imports from nuqs/server internally, so deriving parsers is safe in server
code too.
Single-codec derivation
nuqsParser(codec) is the one-key form, useful when a component owns a
single param:
// @filename: app/products/route.def.ts
import { , } from "paramour";
export const = ("/products", {
: { : .().(1), : .().() },
});
// @filename: app/products/search-box.tsx
"use client";
import { } from "@paramour-js/nuqs";
import { } from "nuqs";
import { } from "paramour";
export function () {
const [, ] = ("q", (.().()));
return < ={() => void (..)} ={ ?? ""} />;
}The derivation rules
The adapter reads presence and defaults off the codec so the nuqs reading matches core's decode:
| Codec shape | nuqs parser |
|---|---|
required or .optional() | nullable read (null = absent) |
.default(value) | withDefault(value) — non-nullable read |
.default(() => value) (factory) | nullable — a frozen default would lie |
.catch(fallback) | malformed reads recover to the fallback |
p.array(…) | multi (repeated-key) parser, [] when absent |
What refuses to compile
nuqs reserves null to mean "absent or unparseable" — so a codec whose
legitimate output includes null has no faithful nuqs twin, and the
adapter rejects it at the call site with the reason in the error, rather
than silently conflating your null with nuqs's:
import { } from "@paramour-js/nuqs";
import { } from "paramour";
const = .<null | string>({
: () => ( === "null" ? null : ),
: () => ?? "null",
});
(maybe);rawSearch routes are rejected the same way (there are no per-key codecs
to derive from), as are search-less routes. Plain-JavaScript callers get the
same judgments as loud runtime errors.
Server and client, one contract
The point of deriving rather than declaring: your server components
parse with the route, your client components
set state through parsers derived from the route, and
standardSearchSchema
exports the same contract to tRPC and friends. One search config, every
consumer.