mrkeyoor.com_
Sat 08 Aug 20:59 UTC
npmWeb Frontendupdated 08 Aug 2026

nuqs

nuqs makes React URL search parameters behave like typed state. Hooks read and update query keys, built-in parsers convert strings to integers, booleans, dates, arrays, JSON, enums, and literals, and server helpers reuse the same parser definitions without client code. Framework adapters connect those operations to Next.js, plain React, Remix, React Router, or TanStack Router. The URL remains the source of truth, which makes filters and pagination shareable, bookmarkable, and compatible with browser navigation.

Verdict

The best fit when URL state is a product feature rather than incidental string parsing, especially across Next.js client and server boundaries. Keep local or sensitive state out of it, and add real validation after parsing untrusted URLs.

API stability4/5The version 2 API consistently centers on useQueryState, useQueryStates, composable parsers, adapters, loaders, caches, and serializers, with types carried through the builder pattern. The main migration cost is architectural rather than a renamed hook: version 2 requires framework adapters, and adapters are split by Next.js and React Router generation. Experimental TanStack Router support and framework routing changes remain areas where consumers should expect movement.
Docs5/5The README and nuqs.dev cover every supported adapter, parser behavior, defaults, history, shallow updates, throttling, React transitions, batching, server caches, loaders, serializers, type inference, testing, debugging, and SEO. Examples state subtle behavior such as null removing keys, defaults not being written, setter Promise caching, and parser output not being validation. Few URL-state libraries document browser and server tradeoffs this directly.
Maintenance5/5Version 2.9.5 was published on August 5, 2026, and GitHub reports a push on August 8, 2026. The repository is not archived, CI is linked from the README, and current development includes explicit adapters for recent Next.js and React Router versions. GitHub's open_issues_count is 49, which combines issues and pull requests; that is a plausible active queue for a project spanning several fast-moving frameworks.
Ecosystem5/5The package records 3,906,133 weekly downloads and supports Next.js App and Pages routers, plain React, Remix, React Router 6 through 8, TanStack Router, and custom adapters. Server-only and testing entry points reduce framework mocking, while Standard Schema support and composable parsers make validation libraries easier to pair. The main boundary is React; non-React applications should use a plain query-string library.

Use it if

  • Search, filters, sorting, tabs, pagination, or map position should survive refreshes and be shareable by URL
  • You want one typed parser definition shared by React hooks, server components, loaders, and link serialization
  • You need batched multi-key updates with deliberate replace-versus-push history behavior
  • Your app uses a supported Next.js, React SPA, Remix, React Router, or TanStack Router adapter
Skip it if

Setup reality

Install with `npm install nuqs`, then add exactly one adapter around the relevant React tree. Next.js App Router and Pages Router use different imports; React Router has versioned v6, v7, and v8 entries; Remix, plain React, and TanStack Router have their own adapters. The package peers on React 18.2 or React 19 plus whichever optional router you actually use, including Next 14.2 or newer. Hooks are client APIs, so Next App Router files using them need `'use client'`; server components should import parsers, loaders, serializers, and caches from `nuqs/server` to avoid the client directive. Missing query keys return null unless a parser uses `.withDefault()`, but that default is internal and is not written into the URL. Setting null removes a key. Parsing is not validation: `parseAsInteger` converts syntax but does not enforce positive ranges, and `parseAsJson<T>()` gives TypeScript a claim rather than checking a runtime schema. Updates replace browser history by default; choose `history: 'push'` only when Back should step through the state changes. Next.js updates are shallow by default, so Server Components and `getServerSideProps` are not notified unless `shallow: false` is set. URL writes are batched and throttled, with a 50 ms floor and stricter Safari behavior, while hook state updates immediately. Await the setter's promise when code must observe the flushed URL. Query strings remain public input, so do not place secrets there and validate server-side before using values in database or authorization decisions.

Patterns

Wrap a Next.js App Router treeinstall-next-adapter

import { NuqsAdapter } from 'nuqs/adapters/next/app'
import type { ReactNode } from 'react'

export default function RootLayout({ children }: { children: ReactNode }) {
  return <html><body><NuqsAdapter>{children}</NuqsAdapter></body></html>
}

Next.js Pages Router uses `nuqs/adapters/next/pages`; choosing the wrong adapter breaks router synchronization.

Bind an input to a query parameterstore-string-param

'use client'
import { useQueryState } from 'nuqs'

const [query, setQuery] = useQueryState('q')
return <input value={query ?? ''} onChange={(event) => setQuery(event.target.value)} />

Without a parser or default, the value is string or null; an empty query value is the empty string, not null.

Parse an integer with a defaultparse-integer-default

import { parseAsInteger, useQueryState } from 'nuqs'

const [page, setPage] = useQueryState(
  'page',
  parseAsInteger.withDefault(1)
)

The default removes null from the TypeScript result but is not written to the URL when the key is absent.

Remove a key from the URLremove-query-param

const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))

await setPage(null)

Setting null deletes the key and returns state to the configured default, if any.

Make Back navigate state changespush-browser-history

const [tab, setTab] = useQueryState('tab', { history: 'push' })

setTab('billing')

History mode is replace by default; push can flood browser history when used for every keystroke.

Re-render Next.js server contentnotify-next-server

const [isPending, startTransition] = useTransition()
const [query, setQuery] = useQueryState(
  'q',
  parseAsString.withOptions({ shallow: false, startTransition })
)

shallow defaults to true. Setting false causes server work, so debounce or throttle high-frequency input.

Throttle high-frequency URL writesthrottle-url-updates

const [value, setValue] = useQueryState('value', {
  throttleMs: 340,
})

React state changes immediately while the URL update is queued; values below the 50 ms floor are ignored.

Batch related query keysupdate-related-params

const [coords, setCoords] = useQueryStates({
  lat: parseAsFloat.withDefault(45.18),
  lng: parseAsFloat.withDefault(5.72),
})

await setCoords({ lat: 51.5, lng: -0.12 })

The setter accepts partial updates and resolves with URLSearchParams after the batched URL flush.

Define a validated custom codeccreate-custom-parser

import { createParser } from 'nuqs'

const parseAsPort = createParser({
  parse(value) {
    const port = Number(value)
    return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null
  },
  serialize: String,
})

Return null for invalid input so callers can fall back to null or a `.withDefault()` value.

Load typed values outside a hookparse-server-params

import { createLoader, parseAsInteger, parseAsString } from 'nuqs/server'

const loadSearch = createLoader({
  q: parseAsString,
  page: parseAsInteger.withDefault(1),
})
const values = loadSearch('?q=printer&page=2')

Loaders accept several URL-like inputs, but parsed values still need business-rule validation.

Build a typed link queryserialize-link-params

import { createSerializer, parseAsInteger, parseAsString } from 'nuqs/server'

const serialize = createSerializer({ q: parseAsString, page: parseAsInteger })
const href = serialize('/search?view=grid', { q: 'laser printer', page: 2 })

Passing null removes an existing key; omitted properties leave the base query unchanged.

Test with the router-free adaptertest-query-state

import { render } from '@testing-library/react'
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'

const onUrlUpdate = vi.fn()
render(<Filters />, {
  wrapper: withNuqsTestingAdapter({
    searchParams: '?page=2',
    onUrlUpdate,
  }),
})

The testing adapter reports URL updates without requiring the application's real framework router.

Alternatives

PackageRegistryPick it when
use-query-paramsnpmChoose it for a mature hook and codec model when its supported router adapters already match your app
query-stringnpmChoose it when framework-independent parsing and stringifying is enough and React state synchronization is unwanted
react-router-domnpmChoose its built-in useSearchParams when a React Router app needs only strings and simple manual conversion