Skip to main content

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:
  1. Hook level - applies to all state updates for that hook
  2. Parser level - using .withOptions() on a parser
  3. 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.
shallow?: boolean
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.

scroll

Controls whether the page scrolls to top after a URL update.
scroll?: boolean
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.
throttleMs?: number
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:
  1. React state updates immediately (optimistic)
  2. URL updates (throttled)
  3. Server Components re-render with new searchParams
  4. isLoading is true while server is responding
  5. 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.
clearOnDefault?: boolean
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

Build docs developers (and LLMs) love