nuqs-url-state
nuqs-url-state is the cogs canonical skill for type-safe URL state management in Next.js with nuqs — parsers as the single source of truth for query-param names, types, and defaults, shared symmetrically between client hooks and server components. It's built around patterns re-derived independently across dozens of real call sites in a production codebase, not generic library documentation, so what it teaches is specifically the mistakes that pattern avoids: a table page silently rendering "no results" when it shouldn't, an off-by-one bug leaking out of a single URL parser, and test mocks that pass even when URL-state wiring is broken.
What it does
The skill walks through the full nuqs surface as used in this codebase's Next.js apps:
- Parsers as the contract —
parseAsString,parseAsInteger,parseAsArrayOf,parseAsStringEnum, and friends define a query param's name, type, and default in exactly one place. - Client usage —
useQueryState/useQueryStatesfor reading and writing URL state from client components. - Server usage —
createSearchParamsCachefor parsing the same parser registry inside server components, so client and server never define search params independently. - Shareable links —
createSerializerfor building URLs (e.g. for<Link>) from a parser registry plus a set of values, instead of hand-building query strings. - A co-location convention (
searchParams.tsnext to the component that uses it), guidance on centralizing shared options (clearOnDefault,shallow) instead of re-declaring them per hook, and a troubleshooting section for hydration mismatches, stale state, and Server Component/client hook confusion.
Why it was created
Documented provenance for this skill comes from the skill's own metadata rather than a dedicated founding dossier — no docs/sessions/ or docs/plans/ writeup exists that narrates why this skill was built (this page's own "Why it was created" section is the closest thing). What the skill's metadata says is specific: the patterns in this skill were mined from a real production codebase's ~58 files importing nuqs, including ~20 near-identical table filter/pagination hooks. That survey is also visible in git history — commit 1888775 (feat(nuqs): add @cogs/nuqs, ported from envmgr-ui's table URL-state patterns) describes extracting the page-index conversion, a createTableUrlState factory, filter/sort parser builders, and a stateful test mock from "a survey of ~20 near-identical use*Filters.ts hooks in environment-manager-ui." A later commit (dfff269) added this skill's metadata.json, formalizing that abstract for npm packaging — but the underlying evidence predates it.
That survey number matters because it's the difference between "this is how nuqs's docs say to do it" and "this is what ~20 independent engineers converged on when nobody was coordinating them" — the latter is much stronger evidence that a pattern is load-bearing rather than a style preference.
How it works
Two patterns in the skill are called out as the ones worth internalizing over everything else:
Pagination is 0-indexed internally, 1-indexed in the URL. A human reading ?page=1 expects the first page, not ?page=0 — but internal state that's 0-indexed matches array indices and most table libraries' PaginationState directly, avoiding off-by-one arithmetic scattered through component code. The skill bridges the two with a single dedicated parser, pageIndexParserStrict, built via createParser with a parse/serialize pair that shifts by one in each direction. The point isn't the shift itself — it's that the shift happens exactly once, in the parser, instead of being re-derived (and occasionally gotten wrong) at every call site that touches page.
Every filter/sort/search setter resets pagination to page 0 as part of the same update — never a separate effect. This is called out in the skill as the single most important pattern, because it fixes a concrete, reproducible bug: a user on page 4 of a filtered table adds another filter, the result set shrinks to one page, but the URL still says page=4 — so the table renders empty even though matching rows exist on page 1. The fix confirmed across ~20 independent real implementations, with zero exceptions, is that setFilters, setSorting, setSearchQuery, and any combined reset function all call setPage(0, options) inline, in the same setter, not behind a useEffect watching those fields (an effect is a render behind, and it's easy to add a new filter field without remembering to add it to the effect's dependency array).
Beyond those two, the skill also documents:
- No import-time side effects in shared parser modules. A
searchParams.tsthat does anything beyond defining parsers/constants at module scope pushes test authors and isolated consumers toward forking a local copy — which then silently drifts from fixes applied to the shared original (like the index-shift fix above). - Stateful test mocks, not no-op setters. Mocking
useQueryState/useQueryStateswith ajest.fn()that does nothing lets tests pass even when a component's pagination or filter controls are completely disconnected from URL state. The recommended fix is a small in-memory store the mock setters actually write to and getters actually read from — including correctly round-tripping custom parsers likepageIndexParserStrict. - Centralizing shared options (
clearOnDefault,shallow) once instead of retyping the same object literal at every hook call site, and treatingstartTransitionusage as a deliberate, codebase-wide decision rather than something that drifts hook-by-hook.
When to use it / when not to
Reach for this skill when a component needs filters, search, sorting, or pagination that should survive a page refresh, be shareable via URL, or be readable from both a server and a client component without redefining the contract twice. It's the right starting point any time you'd otherwise reach for useState to hold view state that a user might reasonably want to bookmark or share.
If you're building a table specifically — pagination and filtering and sorting and search together — use the sibling skill nuqs-table-url-state instead (or in addition): it builds a higher-level hook-factory pattern on top of everything in this skill, wiring the page-reset cascade and the index-shift parser into a single reusable factory rather than having you assemble them by hand per table. This skill is the right level for a single param, a one-off filter set, or understanding the primitives that factory is built from.