Documentation Index
Fetch the complete documentation index at: https://mintlify.com/47ng/nuqs/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Options control how nuqs updates the URL and interacts with your framework’s routing system. They can be set at three levels:
- Hook level - applies to all state updates for that hook
- Parser level - using
.withOptions() on a parser
- Update level - passed to individual
setState calls (overrides hook/parser options)
// Hook level
const [count, setCount] = useQueryState('count', {
history: 'push'
})
// Parser level
const [query, setQuery] = useQueryState(
'q',
parseAsString.withOptions({ history: 'push' })
)
// Update level (overrides hook/parser options)
setCount(42, { history: 'replace' })
Available options
All options are defined in packages/nuqs/src/defs.ts:9-90:
type Options = {
history?: 'replace' | 'push'
scroll?: boolean
shallow?: boolean
throttleMs?: number // deprecated
limitUrlUpdates?: LimitUrlUpdates
startTransition?: TransitionStartFunction
clearOnDefault?: boolean
}
history
Controls how URL updates affect browser history.
history?: 'replace' | 'push'
Default: 'replace'
replace (default)
Replaces the current history entry. Multiple state updates are “squashed” into a single history point:
const [count, setCount] = useQueryState('count', parseAsInteger)
setCount(1) // URL: ?count=1
setCount(2) // URL: ?count=2
setCount(3) // URL: ?count=3
// Clicking back button goes to the page before any count changes
This is ideal for:
- Search/filter controls
- Pagination
- Any state that shouldn’t clutter history
push
Creates a new history entry for each update. Users can navigate state changes with back/forward buttons:
const [step, setStep] = useQueryState('step', {
history: 'push'
})
setStep(1) // URL: ?step=1
setStep(2) // URL: ?step=2
setStep(3) // URL: ?step=3
// Clicking back goes: ?step=3 → ?step=2 → ?step=1
This is ideal for:
- Multi-step forms/wizards
- Tab navigation
- State that represents distinct “pages”
Override per update
const [query, setQuery] = useQueryState('q', { history: 'push' })
// This specific update uses replace instead of push
setQuery(null, { history: 'replace' })
From packages/nuqs/src/defs.ts:10-18 and README.
shallow
This option only applies to Next.js. Other frameworks ignore it.
Controls whether URL updates notify the server.
Default: true
true (default) - Client-only updates
URL updates stay on the client. No network requests are made:
const [filter, setFilter] = useQueryState('filter') // shallow: true by default
setFilter('active')
// ✅ URL updates instantly
// ✅ No re-render of Server Components
// ✅ No network request
This is the recommended default for:
- UI state (filters, sorting, pagination)
- Interactive controls
- Client-side only features
false - Server updates
URL updates trigger a server request and re-render Server Components:
const [filter, setFilter] = useQueryState('filter', {
shallow: false
})
setFilter('active')
// ✅ URL updates
// ✅ Server Components re-render with new searchParams
// ⚠️ Network request to server
This is needed when:
- Server Components depend on the search param
- You need to re-fetch data based on URL state
- SEO requires server-rendered content
From packages/nuqs/src/defs.ts:27-34 and README.
Controls whether the page scrolls to top after a URL update.
Default: false
const [page, setPage] = useQueryState('page', parseAsInteger.withOptions({
scroll: true
}))
setPage(2) // URL updates and page scrolls to top
Unlike Next.js router’s navigation methods which scroll by default, nuqs defaults to false to preserve the user’s scroll position.
From packages/nuqs/src/defs.ts:20-25.
throttleMs (deprecated)
Deprecated in favor of limitUrlUpdates. Use throttle() helper instead.
Limits how frequently the URL is updated (in milliseconds).
// ❌ Deprecated
useQueryState('query', { throttleMs: 1000 })
// ✅ Use instead
import { throttle } from 'nuqs'
useQueryState('query', { limitUrlUpdates: throttle(1000) })
From packages/nuqs/src/defs.ts:37-53.
limitUrlUpdates
Controls rate limiting of URL updates to prevent browser history API throttling.
limitUrlUpdates?: LimitUrlUpdates
type LimitUrlUpdates =
| { method: 'debounce'; timeMs: number }
| { method: 'throttle'; timeMs: number }
Default: throttle(50) (or throttle(120) for Safari)
Why rate limiting?
Browsers rate-limit the History API to prevent abuse:
- Chrome/Firefox: ~50 updates/second is safe
- Safari 17+: max 100 updates per 10 seconds (~120ms throttle recommended)
- Safari <17: max 100 updates per 30 seconds (~320ms throttle recommended)
nuqs automatically detects Safari and adjusts the default:
// From packages/nuqs/src/lib/queues/rate-limiting.ts:6-20
function getDefaultThrottle() {
if (typeof window === 'undefined') return 50
const isSafari = Boolean(window.GestureEvent)
if (!isSafari) {
return 50
}
try {
const match = navigator.userAgent?.match(/version\/([\d\.]+) safari/i)
return parseFloat(match![1]!) >= 17 ? 120 : 320
} catch {
return 320
}
}
Throttle
Ensures a maximum update frequency. State updates are immediate; only URL updates are throttled:
import { throttle } from 'nuqs'
const [query, setQuery] = useQueryState('q', {
limitUrlUpdates: throttle(1000) // Max 1 URL update per second
})
setQuery('a') // State: 'a', URL updates immediately
setQuery('ab') // State: 'ab', URL update queued
setQuery('abc') // State: 'abc', URL update queued
// After 1000ms: URL updates once to ?q=abc
State updates are always instant to keep UI responsive. Only URL/server updates are throttled.
Debounce
Waits for a pause in updates before applying to URL:
import { debounce } from 'nuqs'
const [query, setQuery] = useQueryState('q', {
shallow: false,
limitUrlUpdates: debounce(500) // Wait 500ms of no changes
})
// User types "hello" quickly
setQuery('h') // State: 'h', no URL update yet
setQuery('he') // State: 'he', debounce timer resets
setQuery('hel') // State: 'hel', debounce timer resets
setQuery('hell') // State: 'hell', debounce timer resets
setQuery('hello') // State: 'hello', debounce timer resets
// After 500ms of no typing: URL updates to ?q=hello
Debouncing is ideal for:
- Search inputs (avoid server requests on every keystroke)
- Expensive operations
- Any high-frequency updates
Override per update
const [query, setQuery] = useQueryState('q', {
limitUrlUpdates: throttle(100)
})
// Force immediate update
setQuery('important', { limitUrlUpdates: throttle(0) })
Values lower than 50ms are ignored to prevent rate-limiting issues.
From packages/nuqs/src/defs.ts:55-68 and README.
startTransition
Enables React 18+ transitions for loading states during server updates.
startTransition?: TransitionStartFunction
Only useful when combined with shallow: false in Next.js, or for wrapping navigation events in other frameworks.
Usage with useTransition
'use client'
import React from 'react'
import { useQueryState, parseAsString } from 'nuqs'
function ServerDataComponent() {
// 1. Create transition
const [isLoading, startTransition] = React.useTransition()
// 2. Pass startTransition to parser options
const [query, setQuery] = useQueryState(
'query',
parseAsString.withOptions({
startTransition,
shallow: false // Must be false to trigger server updates
})
)
// 3. Use isLoading to show loading states
if (isLoading) {
return <div>Loading...</div>
}
return (
<input
value={query ?? ''}
onChange={e => setQuery(e.target.value)}
/>
)
}
How it works
When you update state:
- React state updates immediately (optimistic)
- URL updates (throttled)
- Server Components re-render with new searchParams
isLoading is true while server is responding
- Client receives RSC payload and updates
This enables:
- Loading spinners during server updates
- Optimistic UI updates
- Pending states for async operations
From packages/nuqs/src/defs.ts:70-78 and README.
clearOnDefault
Controls whether setting state to the default value clears it from the URL.
Default: true
When a parser has a default value, setting state to that default removes the key from the URL:
const [sort, setSort] = useQueryState(
'sort',
parseAsStringLiteral(['asc', 'desc'] as const).withDefault('asc')
)
setSort('desc') // URL: ?sort=desc
setSort('asc') // URL: (sort removed, back to default)
Why default to true?
Keeps URLs clean by not showing default values:
✅ Clean: /products?filter=sale
❌ Verbose: /products?filter=sale&sort=asc&page=1&limit=20
When to use false
Set to false when you need explicit URLs that don’t change meaning if defaults change:
const [version, setVersion] = useQueryState(
'v',
parseAsInteger.withDefault(1).withOptions({
clearOnDefault: false
})
)
setVersion(1) // URL: ?v=1 (explicit, even though it's the default)
This ensures URLs remain stable even if you change the default value in the future.
From packages/nuqs/src/defs.ts:80-89.
Combining options
You can combine multiple options for fine-grained control:
import { throttle } from 'nuqs'
const [search, setSearch] = useQueryState(
'q',
parseAsString.withOptions({
history: 'push', // Each search creates history entry
shallow: false, // Notify server
scroll: false, // Don't scroll to top
limitUrlUpdates: throttle(500), // Max 2 updates/sec
clearOnDefault: true // Remove ?q when empty
})
)
Option precedence
When the same option is specified at multiple levels:
const parser = parseAsString.withOptions({ history: 'push' })
const [state, setState] = useQueryState('key', {
...parser,
history: 'replace' // Hook level overrides parser level
})
setState('value', {
history: 'push' // Update level overrides both
})
Precedence: Update level > Hook level > Parser level > Default