nuqs@2.5.0 is available, try it now:
npm install nuqs@latest
It’s a big release full of long-awaited features, bug fixes & improvements, including:
- ⏱️ Debounce: only send network requests once users stopped typing in search inputs
- ☑️ Standard Schema: connect validation & type inference to external tools (eg: tRPC)
- ⚡ Key isolation: only re-render components when their part of the URL changes
- 🏝️ TanStack Router support with type-safe routing (🧪 experimental)
Debounce
While nuqs has always had a throttling system in place to adapt to browers rate-limiting URL updates,
this system wasn’t ideal for high frequency inputs, like <input type="search">
, or <input type="range">
sliders.
For those cases where the final value is what matters, debouncing makes more sense than throttling.
Do I need debounce?
Debounce only makes sense for server-side data fetching (RSCs & loaders, when combined with shallow: false
),
to control when requests are made to the server. For example: it lets you avoid sending the first
character on its own when typing in a search input, by waiting for the user to finish typing.
The state returned by the hooks is always updated immediately: only the network requests sent to the server are debounced.
If you are fetching client-side (eg: with TanStack Query), you’ll want to debounce the
returned state instead (using a 3rd party useDebounce
utility hook).
You can now specify a new option, limitUrlUpdates
, that replaces throttleMs
and declares either a debouncing or throttling behaviour:
import { debounce, useQueryState } from 'nuqs'
function DebouncedSearchInput() {
// Send updates to the server after 250ms of inactivity
const [search, setSearch] = useQueryState('search', {
defaultValue: '',
shallow: false,
limitUrlUpdates: debounce(250)
})
// You can still use controlled components:
// the local state updates instantly.
return (
<input
type="search"
value={search}
onChange={e => setSearch(e.target.value)}
/>
)
}
Read the complete documentation for the API, explanations of what it does, and a list of tips when working with search inputs (you might not want to always debounce).
Standard Schema
You can now use your search params definitions objects
(the ones you feed to
useQueryStates
,
createLoader
and
createSerializer
) to derive a Standard Schema validator,
that you can use for basic runtime validation and type inference with other tools, like:
- tRPC, to validate procedure inputs when feeding them your URL state
- TanStack Router’s
validateSearch
(see below)
import {
createStandardSchemaV1,
parseAsInteger,
parseAsString,
} from 'nuqs'
// 1. Define your search params as usual
export const searchParams = {
searchTerm: parseAsString.withDefault(''),
maxResults: parseAsInteger.withDefault(10)
}
// 2. Create a Standard Schema compatible validator
export const validateSearchParams = createStandardSchemaV1(searchParams)
// 3. Use it with other tools, like tRPC:
router({
search: publicProcedure.input(validateSearchParams).query(...)
})
Read the complete documentation for the options you can pass in.
Key isolation
Also known as fine grained subscriptions, and pioneered by TanStack Router, key isolation is the idea that components listening to a search param key in the URL should only re-render when the value for that key changes.
Without key isolation, any change of the URL re-renders every component listening to it.
Take a look at those two counter buttons, and how clicking one re-renders both, without key isolation:
And this is what happens with key isolation:
Key isolation is now built-in for the following adapters:
- React SPA
- React Router (v6 & v7)
- Remix
- TanStack Router
No Next.js? 😢
Unfortunately, Next.js uses a single Context to carry a URLSearchParams
object, which changes
reference whenever any search params change, and therefore re-renders every useSearchParams
call site.
I’m working with the Next.js team to find solutions to this issue to improve performance for everyone (not just nuqs users).
TanStack Router
We’ve added experimental support for TanStack Router, so you can load and use nuqs-enabled components from NPM, or shared between different frameworks in a monorepo.
TanStack Router already has great APIs for type-safe URL state management, and we encourage you to use those in your application code. This adapter serves mainly as a compatibility layer.
This also includes limited support for connecting nuqs search params definitions to TSR’s type-safe routing, via the Standard Schema interface.
Refer to the complete documentation for what is supported.
Other changes
Global defaults for options
You can now specify different defaults for some options, at the adapter level:
<NuqsAdapter
defaultOptions={{
shallow: false, // Always send network requests on updates
scroll: true, // Always scroll to the top of the page on updates
clearOnDefault: false, // Keep default values in the URL
limitUrlUpdates: throttle(250), // Increase global throttle
}}
>
{children}
</NuqsAdapter>
Preview support for Next.js 15.5 typed routes
Type-safe routing is now available as an option in Next.js 15.5.
While I’m still working on designing an API to support this elegantly, a little change to the serializer types can allow you to experiment with it in userland, using a copy-pastable utility function:
// Copy this in your codebase
import { Route } from 'next'
import {
createSerializer,
type CreateSerializerOptions,
type ParserMap
} from 'nuqs/server'
export function createTypedLink<Parsers extends ParserMap>(
route: Route,
parsers: Parsers,
options: CreateSerializerOptions<Parsers> = {}
) {
const serialize = createSerializer<Parsers, Route, Route>(parsers, options)
return serialize.bind(null, route)
}
Usage:
import { createTypedLink } from '@/src/typed-links'
import { parseAsFloat, parseAsIsoDate, parseAsString, type UrlKeys } from 'nuqs'
// Reuse your search params definitions objects & urlKeys:
const searchParams = {
latitude: parseAsFloat.withDefault(0),
longitude: parseAsFloat.withDefault(0),
}
const urlKeys: UrlKeys<typeof searchParams> = {
// Define shorthands in the URL
latitude: 'lat',
longitude: 'lng'
}
// This is a function bound to /map, with those search params & mapping:
const getMapLink = createTypedLink('/map', searchParams, { urlKeys })
function MapLinks() {
return (
<Link
href={
getMapLink({ latitude: 48.86, longitude: 2.35 })
// → /map?lat=48.86&lng=2.35
}
>
Paris, France
</Link>
)
}
This is based on the same technique I used on React Router’s type-safe href
utility in this video:
I’ll open an RFC discussion soon to define the API, with the goals in mind that:
- It should support both Next.js & React Router typed routes (if we could connect to TSR too that’d be nice 👀)
- It should handle static, dynamic & catch-all routes, with type-safe pathname params, search params, and hash.
Dependencies & bundle size
nuqs is now a zero runtime dependencies library! 🙌
While this release packed a lot of new features, we kept it under 5.5kB (minified + gzipped).
Full changelog
Features
- #855Key isolation
byfranky47
- #900Debounce
byfranky47
- #953Add support for TanStack Router
byahmedrowaihi
- #965Add Standard Schema interface
byfranky47
- #1038Add `const` modifier to literal parsers to auto-infer their arguments as literals
byneefrehman
- #1062Export ./package.json in exports field for Module Federation support
byAfeefRazick
- #1066defaultOptions for NuqsAdapter
byTkDodo
- #1079Add support for more global default options at the adapter level
byfranky47
- #1083Allow specifying a different base type for the serializer
byfranky47
Bug fixes
- #996Replace require by default conditional export field
bystefan-schubert-sbb
(helps with ESM/CJS interop) - #1057Type inference for defaultValue of object syntax
byTkDodo
- #1063Remove esm-only on TanStack Router export
byfranky47
- #1073Handle JSON in TanStack Router
byfranky47
Documentation
- #787Add inertia community adapter
byJoehoel→ Read the docs
- #976Add blog section
byfranky47
- #1000Vercel OSS program
byfranky47
- #1004Add code.store & oxom as sponsors 💖
byfranky47
- #1005The URL type-safety iceberg
byfranky47
- #1017Fix non-null assertions
byfranky47
- #1021Add Deploy on Vercel button
byfranky47
- #1025Extend next-app example to include more features
byI-3B
- #1027Fix mobile navbar collapse & sticky
byfranky47
- #1032Fix 500 error on Vercel ISR
byfranky47
- #1037Add Aurora Scharff as a sponsor 💖
byfranky47
- #1041Fix transition docs to not call parser as function
byphelma
- #1043Title is hidden behind headers on mobile
byawosky
- #1046Update NUQS-404.md
bydmytro-palaniichuk
- #1051add effect schema parser page
byethanniser
- #1052Debounce docs edits
byfranky47
- #1056Migrate docs to Fumadocs 15, Tailwind CSS v4
byfuma-nama
- #1058Fix default value for shallow in React Router disclaimer
byfranky47
- #1070prevent NaN appearing in pagination example
by87xie
- #1082Add nuqs 2.5 release blog post
byfranky47
Other changes
- #985Use React Compiler RC
byfranky47
- #990Replace tsup with tsdown
byfranky47
- #1011Add RSS feed auto-discovery
byfranky47
- #1029Fix API stability test
byfranky47
- #1033Linting PR titles
byfranky47
- #1065Add TanStack Router to og:images
byfranky47
- #1067Improve stats page
byfranky47
- #1074Configure MDX types
byremcohaszing
- #1077Track beta versions adoption in the stats page
byfranky47
- #1078Update dependencies
byfranky47
- #1080Pass children as config in createElement to avoid ts-expect-error
byfranky47
- #1081Reduce the bundle size
byfranky47
- #1086Add publint step to CI workflow
byAmirmohammad-BashiriCongrats on your first OSS contribution! 🙌
What’s next?
Long standing issues and feature requests include:
- Support for native arrays by repeating keys in the URL (eg:
?foo=bar&foo=egg
gives you['bar', 'egg']
) - Runtime validation with Standard Schema (Zod, Valibot, ArkType etc), to validate what TypeScript can’t represent (like number ranges & string formats).
- Support for typed links in Next.js 15.5 and React Router’s
href
utility.
Thanks
I want to thank sponsors, contributors and people who raised issues, discussions and reviewed PRs on GitHub, Bluesky and X/Twitter. You are the growing community that drives this project forward, and I couldn’t be happier with the response.
Sponsors
Thanks to these amazing people and companies, I’m able to dedicate more time to this project and make it better for everyone. Join them on 💖 GitHub Sponsors!
Contributors
Huge thanks to @87xie, @AfeefRazick, @ahmedrowaihi, @Amirmohammad-Bashiri, @AmruthPillai, @an-h2, @anhskohbo, @awosky, @brandanking-decently, @devhasson, @didemkkaslan, @dinogit, @dmytro-palaniichuk, @Elya29, @ericwang401, @ethanniser, @fuma-nama, @gensmusic, @I-3B, @jaberamin9, @Joehoel, @Kavan72, @krisnaw, @Manjit2003, @neefrehman, @phelma, @remcohaszing, @SeanCassiere, @snelsi, @stefan-schubert-sbb, @thewebartisan7, @TkDodo, @vanquishkuso, and @Willem-Jaap for helping!