SuperAPI AI Directory

nuqs

Testing & quality

View repository

Updated 10-15 21:41

About this skill

Build and review query state with nuqs

Treat the URL as the state contract. Follow the steps in order, and load only the reference branches whose conditions match the application.

1. Model the URL contract

For every query key, record:

  • its URL spelling and domain type;
  • the value to use when the key is absent or invalid;
  • whether setting the default should remove the key;
  • whether an update represents navigation or ephemeral UI state;
  • which client components, loaders, routes, or server components read it.

Inspect the installed nuqs, framework, and router major versions before choosing integration paths.

Read the matching branch references:

Complete when every query key has one documented URL representation and the adapter import and provider boundary match the installed router.

2. Define one parser contract

Put related parsers in a dependency-neutral module. Import parsers from nuqs/server when any server-side consumer imports that module; client hooks can reuse the exported parser object.

Prefer built-ins that match the wire format. Use .withDefault(value) only when absent and invalid values should resolve to a non-null domain default. Defaults stay internal unless explicitly written; setting state to null removes the query key.

import {
  parseAsInteger,
  parseAsString,
  parseAsStringLiteral,
} from "nuqs/server";

export const searchParams = {
  q: parseAsString.withDefault(""),
  page: parseAsInteger.withDefault(1),
  sort: parseAsStringLiteral(["relevance", "date"] as const).withDefault(
    "relevance",
  ),
};

Create a custom parser only for a wire format that built-ins cannot express. Its parse function returns null for invalid input, and parsing and serialization form a pure, lossless round trip for every valid value.

Read the matching branch references:

Complete when every key has exactly one parser, nullability follows its default policy, invalid input has an explicit outcome, and all consumers import the same parser definition.

3. Bind the contract to client state

Use useQueryState for one independent key. Use useQueryStates when keys form one state object or must update atomically. Keep the URL-backed value as the source of truth; isolate any temporary input draft and define when it commits back to the URL. Client hook modules carry the 'use client' directive where the framework requires it.

Choose update semantics deliberately:

  • Keep the default history: 'replace' for ephemeral state. Use 'push' when each change should become a Back-button navigation point.
  • Keep shallow client-first updates when the server does not need the new value. Use shallow: false only when an update must rerun a server component or route loader.
  • Enable scroll only for navigation that should move the viewport.
  • Keep the default clearOnDefault: true for canonical URLs. Set it to false only when the URL must preserve an explicitly written default value.
  • Use limitUrlUpdates: throttle(ms) to bound repeated URL or server updates. Use debounce(ms) for server-side fetching after the user pauses; debounce the hook value separately for client-side fetching.
  • Resolve option conflicts by precedence: setter call, parser, then hook-level options.

Read the matching branch references:

Complete when each updater has explicit removal, history, server-notification, scroll, and rate-limit behavior for every key it can change.

4. Parse at server boundaries when present

Reuse the parser contract at every loader, request handler, route validator, or Server Component that reads query state. Prefer createLoader for entry-point parsing. Use createSearchParamsCache only when nested Next.js Server Components need parsed values without prop drilling. When there is no server-side reader, confirm that the query-state behavior is intentionally client-first.

Read the matching branch references:

Complete when every server boundary parses the shared contract before reading values, or every key is confirmed to have no server consumer.

5. Verify the contract

Run the application's focused typecheck and tests. Exercise representative URLs for absent, valid, empty, and invalid values, then update and clear every key. Verify reload, copied-link, Back/Forward, and server refresh behavior for the selected options. For high-frequency shallow: false updates, also verify the request cadence.

Read the matching branch references:

Complete when tests and manual checks account for every query key and prove its parse, serialize, default, clear, navigation, and server-notification behavior.