MCPcopy Index your code
hub / github.com/47ng/nuqs

github.com/47ng/nuqs @v2.9.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v2.9.0 ↗ · + Follow
1,361 symbols 4,543 edges 1,166 files 11 documented · 1% 21 cross-repo links updated todayv2.9.0 · 2026-06-30★ 10,64620 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README
<img alt="nuqs" src="https://raw.githubusercontent.com/47ng/nuqs/next/packages/res/wordmark.light.svg" width="384">

npm version

npm downloads

MIT License

CI/CD

Last commit

GitHub Sponsors

Type-safe search params state manager for React frameworks. Like useState, but stored in the URL query string.

Features

  • 🔀 new: Supports Next.js (app and pages routers), plain React (SPA), Remix, React Router, TanStack Router, and custom routers via adapters
  • 🧘‍♀️ Simple: the URL is the source of truth
  • 🕰 Replace history or append to use the Back button to navigate state updates
  • ⚡️ Built-in parsers for common state types (integer, float, boolean, Date, and more). Create your own parsers for custom types & pretty URLs
  • ♊️ Related querystrings with useQueryStates
  • 📡 Shallow mode by default for URL query updates, opt-in to notify server components
  • 🗃 Server cache for type-safe searchParams access in nested server components
  • ⌛️ Support for useTransition to get loading states on server updates

Documentation

Read the complete documentation at nuqs.dev.

Installation

npm install nuqs
pnpm add nuqs
yarn add nuqs
bun add nuqs
deno add nuqs
vlt install nuqs

Adapters

You will need to wrap your React component tree with an adapter for your framework. (expand the appropriate section below)

▲ Next.js (app router)

Supported Next.js versions: >=14.2.0. For older versions, install nuqs@^1 (which doesn't need this adapter code).

// src/app/layout.tsx
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)

Supported Next.js versions: >=14.2.0. For older versions, install nuqs@^1 (which doesn't need this adapter code).

// src/pages/_app.tsx
import type { AppProps } from 'next/app'
import { NuqsAdapter } from 'nuqs/adapters/next/pages'

export default function MyApp({ Component, pageProps }: AppProps) {
  return (
    <NuqsAdapter>
      <Component {...pageProps} />
    </NuqsAdapter>
  )
}

⚛️ Plain React (SPA)

Example: via Vite or create-react-app.

import { NuqsAdapter } from 'nuqs/adapters/react'

createRoot(document.getElementById('root')!).render(
  <NuqsAdapter>
    <App />
  </NuqsAdapter>
)

💿 Remix

Supported Remix versions: @remix-run/react@>=2

// app/root.tsx
import { NuqsAdapter } from 'nuqs/adapters/remix'

// ...

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}

React Router v6

Supported React Router versions: react-router-dom@^6

import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import App from './App'

const router = createBrowserRouter([
  {
    path: '/',
    element: <App />
  }
])

export function ReactRouter() {
  return (
    <NuqsAdapter>
      <RouterProvider router={router} />
    </NuqsAdapter>
  )
}

React Router v7

Supported React Router versions: react-router@^7

// app/root.tsx
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { Outlet } from 'react-router'

// ...

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}

React Router v8

Supported React Router versions: react-router@^8

// app/root.tsx
import { NuqsAdapter } from 'nuqs/adapters/react-router/v8'
import { Outlet } from 'react-router'

// ...

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}

🏝️ TanStack Router

Supported TanStack Router versions: @tanstack/react-router@^1 Note: TanStack Router support is experimental and does not yet cover TanStack Start.

// src/routes/__root.tsx
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router'
import { Outlet, createRootRoute } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: () => (
    <>
      <NuqsAdapter>
        <Outlet />
      </NuqsAdapter>
    </>
  )
})

Usage

'use client' // Only works in client components

import { useQueryState } from 'nuqs'

export default () => {
  const [name, setName] = useQueryState('name')
  return (
    <>
      <h1>Hello, {name || 'anonymous visitor'}!</h1>
      <input value={name || ''} onChange={e => setName(e.target.value)} />
      <button onClick={() => setName(null)}>Clear</button>
    </>
  )
}

useQueryState takes one required argument: the key to use in the query string.

Like React.useState, it returns an array with the value present in the query string as a string (or null if none was found), and a state updater function.

Example outputs for our hello world example:

URL name value Notes
/ null No name key in URL
/?name= '' Empty string
/?name=foo 'foo'
/?name=2 '2' Always returns a string by default, see Parsing below

Parsing

If your state type is not a string, you must pass a parsing function in the second argument object.

We provide parsers for common and more advanced object types:

import {
  parseAsString,
  parseAsInteger,
  parseAsFloat,
  parseAsBoolean,
  parseAsTimestamp,
  parseAsIsoDateTime,
  parseAsArrayOf,
  parseAsJson,
  parseAsStringEnum,
  parseAsStringLiteral,
  parseAsNumberLiteral
} from 'nuqs'

useQueryState('tag') // defaults to string
useQueryState('count', parseAsInteger)
useQueryState('brightness', parseAsFloat)
useQueryState('darkMode', parseAsBoolean)
useQueryState('after', parseAsTimestamp) // state is a Date
useQueryState('date', parseAsIsoDateTime) // state is a Date
useQueryState('array', parseAsArrayOf(parseAsInteger)) // state is number[]
useQueryState('json', parseAsJson<Point>()) // state is a Point

// Enums (string-based only)
enum Direction {
  up = 'UP',
  down = 'DOWN',
  left = 'LEFT',
  right = 'RIGHT'
}

const [direction, setDirection] = useQueryState(
  'direction',
  parseAsStringEnum<Direction>(Object.values(Direction)) // pass a list of allowed values
    .withDefault(Direction.up)
)

// Literals (string-based only)
const colors = ['red', 'green', 'blue'] as const

const [color, setColor] = useQueryState(
  'color',
  parseAsStringLiteral(colors) // pass a readonly list of allowed values
    .withDefault('red')
)

// Literals (number-based only)
const diceSides = [1, 2, 3, 4, 5, 6] as const

const [side, setSide] = useQueryState(
  'side',
  parseAsNumberLiteral(diceSides) // pass a readonly list of allowed values
    .withDefault(4)
)

You may pass a custom set of parse and serialize functions:

import { useQueryState } from 'nuqs'

export default () => {
  const [hex, setHex] = useQueryState('hex', {
    // TypeScript will automatically infer it's a number
    // based on what `parse` returns.
    parse: (query: string) => parseInt(query, 16),
    serialize: value => value.toString(16)
  })
}

Default value

When the query string is not present in the URL, the default behaviour is to return null as state.

It can make state updating and UI rendering tedious. Take this example of a simple counter stored in the URL:

import { useQueryState, parseAsInteger } from 'nuqs'

export default () => {
  const [count, setCount] = useQueryState('count', parseAsInteger)
  return (
    <>
      <pre>count: {count}</pre>
      <button onClick={() => setCount(0)}>Reset</button>
      {/* handling null values in setCount is annoying: */}
      <button onClick={() => setCount(c => c ?? 0 + 1)}>+</button>
      <button onClick={() => setCount(c => c ?? 0 - 1)}>-</button>
      <button onClick={() => setCount(null)}>Clear</button>
    </>
  )
}

You can specify a default value to be returned in this case:

const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))

const increment = () => setCount(c => c + 1) // c will never be null
const decrement = () => setCount(c => c - 1) // c will never be null
const clearCount = () => setCount(null) // Remove query from the URL

Note: the default value is internal to React, it will not be written to the URL.

Setting the state to null will remove the key in the query string and set the state to the default value.

Options

History

By default, state updates are done by replacing the current history entry with the updated query when state changes.

You can see this as a sort of git squash, where all state-changing operations are merged into a single history value.

You can also opt-in to push a new history item for each state change, per key, which will let you use the Back button to navigate state updates:

// Default: replace current history with new state
useQueryState('foo', { history: 'replace' })

// Append state changes to history:
useQueryState('foo', { history: 'push' })

Any other value for the history option will fallback to the default.

You can also override the history mode when calling the state updater function:

const [query, setQuery] = useQueryState('q', { history: 'push' })

// This overrides the hook declaration setting:
setQuery(null, { history: 'replace' })

Shallow

Note: this feature only applies to Next.js

By default, query state updates are done in a client-first manner: there are no network calls to the server.

This is equivalent to the shallow option of the Next.js pages router set to true, or going through the experimental windowHistorySupport flag in the app router.

To opt-in to query updates notifying the server (to re-run getServerSideProps in the pages router and re-render Server Components on the app router), you can set shallow to false:

const [state, setState] = useQueryState('foo', { shallow: false })

// You can also pass the option on calls to setState:
setState('bar', { shallow: false })

Throt

Extension points exported contracts — how you extend this code

Window (Interface)
(no doc)
packages/nuqs/src/adapters/lib/context.ts
ButtonProps (Interface)
(no doc)
packages/docs/src/components/ui/button.tsx
GetUsersOptions (Interface)
(no doc)
packages/examples/next-app/src/data.ts
Register (Interface)
(no doc)
packages/e2e/tanstack-router/src/main.tsx
History (Interface)
(no doc)
packages/nuqs/src/adapters/lib/patch-history.ts
BadgeProps (Interface)
(no doc)
packages/docs/src/components/ui/badge.tsx
Window (Interface)
(no doc)
packages/nuqs/src/adapters/next/impl.pages.ts

Core symbols most depended-on inside this repo

useQueryState
called by 190
packages/nuqs/src/useQueryState.ts
push
called by 149
packages/nuqs/src/lib/queues/throttle.ts
useQueryStates
called by 116
packages/nuqs/src/useQueryStates.ts
navigateTo
called by 116
packages/e2e/shared/playwright/navigate.ts
cn
called by 115
packages/docs/src/lib/utils.ts
load
called by 82
packages/e2e/react-router/v6/src/react-router.tsx
withNuqsTestingAdapter
called by 69
packages/nuqs/src/adapters/testing.ts
createSerializer
called by 47
packages/nuqs/src/serializer.ts

Shape

Function 1,299
Method 40
Class 12
Interface 7
Enum 3

Languages

TypeScript100%

Modules by API surface

packages/e2e/shared/playwright/reporter.ts24 symbols
packages/docs/content/docs/parsers/demos.tsx22 symbols
packages/scripts/lib/commit-graph.ts20 symbols
packages/scripts/release-finalize.ts19 symbols
packages/scripts/verify-release/verify.lib.ts17 symbols
packages/nuqs/src/parsers.ts14 symbols
packages/docs/src/components/feature-support-matrix.tsx14 symbols
packages/nuqs/src/lib/queues/debounce.ts13 symbols
packages/scripts/lib/changelog-dto.ts12 symbols
packages/nuqs/src/lib/queues/throttle.ts12 symbols
packages/docs/src/app/(pages)/stats/_components/partial-line.tsx11 symbols
packages/scripts/lib/commit-graph.test.ts10 symbols

Dependencies from manifests, versioned

@commitlint/config-conventional20.4.1 · 1×
@databuddy/sdk2.3.29 · 1×
@faker-js/faker10.2.0 · 1×
@headlessui/tailwindcss0.2.2 · 1×
@icons-pack/react-simple-icons13.8.0 · 1×
@number-flow/react0.5.11 · 1×
@octokit/plugin-throttling11.0.3 · 1×
@playwright/testcatalog:e2e · 1×
@radix-ui/react-checkbox1.3.3 · 1×
@radix-ui/react-context-menu2.2.16 · 1×
@radix-ui/react-label2.1.8 · 1×
@radix-ui/react-popover1.1.15 · 1×

For agents

$ claude mcp add nuqs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact