Skip to main content

Overview

When you update multiple search params in quick succession, nuqs automatically batches them into a single URL update. This prevents redundant history entries and avoids browser rate limiting.

How batching works

Event loop batching

All state updates in the same JavaScript event loop tick are automatically batched:
From the README:
“You can call as many state update functions as needed in a single event loop tick, and they will be applied to the URL asynchronously.”

The update queue

Internally, nuqs uses a ThrottledQueue to manage updates:
When you call setState:
  1. Update is added to the queue
  2. If another update for the same key exists, it’s replaced
  3. Options are merged (preserving the most permissive settings)
  4. A flush is scheduled for the next tick
From packages/nuqs/src/lib/queues/throttle.ts:45-72:

Flushing the queue

The queue flushes on the next event loop tick, respecting the throttle limit:

Throttling

Default throttle

To prevent browser rate limiting, nuqs throttles URL updates:
  • Chrome/Firefox: 50ms between updates (default)
  • Safari 17+: 120ms between updates
  • Safari <17: 320ms between updates
The default is automatically detected:

Why throttle?

Browsers rate-limit history API calls to prevent abuse:
  • Safari allows only 100 updates per 10 seconds (Safari 17+) or 30 seconds (older)
  • Exceeding this causes errors and breaks navigation
Throttling only affects URL updates and server requests. State updates are always immediate to keep UI responsive.

Custom throttle values

You can override the default throttle:

Minimum throttle value

Values below 50ms are ignored:
This prevents rate-limiting errors even if you try to disable throttling.

Debouncing

For high-frequency updates (like text input), debouncing waits for a pause before updating the URL:
Behavior:
  • User types “h” → state updates, debounce starts
  • User types “e” → state updates, debounce resets
  • User types “llo” → state updates, debounce resets
  • 300ms passes with no typing → URL updates to ?q=hello
This is especially useful with shallow: false to avoid server requests on every keystroke.

Await URL updates

All setState functions return a Promise that resolves when the URL has been updated:
From the README:
“If you wish to know when the URL has been updated, and what it contains, you can await the Promise returned by the state updater function, which gives you the updated URLSearchParameters object.”

Promise caching

The returned Promise is cached until the next flush:
From the README:
“The returned Promise is cached until the next flush to the URL occurs, so all calls to a setState (of any hook) in the same event loop tick will return the same Promise reference.”

Option merging

When batching updates with different options, nuqs merges them intelligently:

History mode

If any update uses push, all updates use push:
From packages/nuqs/src/lib/queues/throttle.ts:56-58:

Scroll option

If any update requests scroll, the page scrolls:

Shallow mode

If any update uses shallow: false, the server is notified:

Throttle time

The highest throttle value wins:
From packages/nuqs/src/lib/queues/throttle.ts:68-71:

Using useQueryStates

For related query params that should always update together, use useQueryStates:
From the README.

Performance implications

State updates: O(1)

State updates are synchronous and immediate:

URL updates: throttled

URL updates are async and throttled:

Memory usage

The queue stores only the latest value for each key:

Debugging batching

Enable debug logs to see batching in action:
You’ll see logs like:
From the README debugging section.

Edge cases

Aborting pending updates

Navigating away or unmounting aborts pending updates:

Infinite throttle

Setting throttleMs: Infinity prevents URL updates entirely:
From packages/nuqs/src/lib/queues/throttle.ts:93-96:

Concurrent rendering

Adapters control whether to reset the queue immediately or wait:
Next.js sets this to false to handle React 18’s concurrent rendering:
This prevents race conditions during Suspense boundaries and concurrent updates.