
Nuqs
- 54 installs
- 2 repo stars
- Updated August 3, 2026
- fandhe-ai/agent-reference-skills
Helps with ai & agent building tasks.
About
nuqs is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- nuqs
- AI & Agent Building
- AI-coding skill
Nuqs by the numbers
- 54 all-time installs (skills.sh)
- Ranked #6,946 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/fandhe-ai/agent-reference-skills --skill nuqsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 54 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 3, 2026 |
| Repository | fandhe-ai/agent-reference-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
nuqs API リファレンス
nuqs — Type-safe URL query state management for React。 React.useState のドロップイン置換として URL クエリパラメータと状態を同期する。
ディレクトリ構成
skills/nuqs/
SKILL.md
references/
hooks/
README.md
useQueryState.md
useQueryStates.md
parsers/
README.md
built-in.md
custom.md
options/
README.md
options.md
server/
README.md
server-side.md
samples/
README.md
basic-query-state.md
typed-state-with-parsers.md
multiple-params-batch-update.md
search-filter-debounce.md
server-side-parsing.md
search-params-cache.md
adapter-setup.md
custom-parser.md
url-key-remapping.md
array-params.md
testing.md
scripts/
README.md
install.md
setup.md
testing.md
debug.md探索手順
タスクからカテゴリを引き、カテゴリの README.md で目的のページを特定する:
1. 下記マッピング表でタスクに対応するカテゴリを探す 2. そのカテゴリの references/{category}/README.md を参照して目的のページを特定する 3. 該当ページの .md を Read して詳細を確認する
タスク → カテゴリ マッピング
| タスク | カテゴリ | 参照 README |
|---|---|---|
| useQueryState の基本的な使い方、単一パラメータの読み書き | hooks | references/hooks/README.md |
| useQueryStates でバッチ更新、複数パラメータの同期 | hooks | references/hooks/README.md |
| ビルトインパーサー(parseAsString / parseAsInteger / parseAsBoolean / parseAsArrayOf 等)の使い方 | parsers | references/parsers/README.md |
| createParser でカスタムパーサーを作成する | parsers | references/parsers/README.md |
| history, shallow, scroll, throttle, debounce, clearOnDefault, startTransition オプション設定 | options | references/options/README.md |
| createLoader, createSearchParamsCache でサーバーサイド解析 | server | references/server/README.md |
| 典型的な使い方を知りたい | samples | samples/README.md |
| インストール・CLI コマンド・テスト・デバッグ手順を知りたい | scripts | scripts/README.md |
hooks
| Name | Description | Path |
|---|---|---|
| useQueryState | React.useState のドロップイン置換。単一の URL クエリパラメータと React state を同期する。 | useQueryState.md |
| useQueryStates | 複数のクエリパラメータをアトミックに読み書きする。同一イベントループ内の更新はバッチ処理され、1回の URL 更新にまとめられる。 | useQueryStates.md |
useQueryState
React.useState のドロップイン置換。単一の URL クエリパラメータと React state を同期する。
シグネチャ
import { useQueryState } from 'nuqs'
const [value, setValue] = useQueryState(key, parserOrOptions?)| 引数 | 型 | 説明 |
|---|---|---|
key | string | クエリパラメータ名 |
parserOrOptions | `Parser \ | Options` |
戻り値
[value, setValue] — React.useState と同じタプル形式。
value: クエリパラメータの現在値(デフォルトではstring | null)setValue: 状態更新関数。Promise<URLSearchParams>を返す
URL と値の対応
| URL | value | 備考 |
|---|---|---|
/ | null | キーが URL に存在しない |
/?name= | '' | 空文字列 |
/?name=foo | 'foo' | 文字列値 |
/?name=2 | '2' | デフォルトでは常に string。パーサーで型変換する |
基本例
'use client'
import { useQueryState } from 'nuqs'
export function Demo() {
const [name, setName] = useQueryState('name')
return (
<>
<input value={name || ''} onChange={e => setName(e.target.value)} />
<button onClick={() => setName(null)}>Clear</button>
<p>Hello, {name || 'anonymous visitor'}!</p>
</>
)
}パーサーを使った型付き state
import { useQueryState, parseAsInteger } from 'nuqs'
const [count, setCount] = useQueryState('count', parseAsInteger)
// count: number | nullデフォルト値
null の代わりにデフォルト値を返す。デフォルト値がある場合、戻り値の型から null が除去される。
// .withDefault() メソッド(推奨)
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))
// count: number(null にならない)
// options オブジェクト
const [search] = useQueryState('search', { defaultValue: '' })
// search: string(null にならない)setState の動作
// 値をセット
setName('foo') // → ?name=foo
// null をセットするとキーを URL から削除
setName(null) // → / (キー削除)
// 関数型アップデート
setCount(prev => prev + 1)
// call-level オプション
setName('bar', { history: 'push', scroll: true })
// Promise を返す
const searchParams = await setName('baz')注意事項
nullをセットするとクエリパラメータが URL から削除される- デフォルト値は React 内部でのみ使用される。URL には書き込まれない(
clearOnDefault: trueの場合) - パーサーが無効な値を受け取った場合、デフォルト値(または
null)が返される
関連
- useQueryStates
- パーサー
- オプション
useQueryStates
複数のクエリパラメータをアトミックに読み書きする。同一イベントループ内の更新はバッチ処理され、1回の URL 更新にまとめられる。
シグネチャ
import { useQueryStates } from 'nuqs'
const [state, setState] = useQueryStates(keyMap, options?)| 引数 | 型 | 説明 |
|---|---|---|
keyMap | Record<string, Parser> | キーとパーサーのマッピング |
options | Options | グローバルオプション(省略可) |
戻り値
[state, setState]
state: 各キーの現在値を持つオブジェクトsetState: 全部または一部のキーを更新する関数。Promise<URLSearchParams>を返す
基本例
import { useQueryStates, parseAsFloat } from 'nuqs'
const [coordinates, setCoordinates] = useQueryStates(
{
lat: parseAsFloat.withDefault(45.18),
lng: parseAsFloat.withDefault(5.72),
},
{
history: 'push',
}
)
const { lat, lng } = coordinates
// 一部のキーを更新
await setCoordinates({ lat: 42, lng: 12 })
// 全キーをクリア(管理外のパラメータは維持)
setCoordinates(null)バッチ処理
同一イベントループ内で複数の useQueryState setter を呼んだ場合も自動的にバッチされる:
setLat(Math.random() * 180 - 90)
setLng(Math.random() * 360 - 180)
// 2つの更新が1つの history エントリにまとめられるsetter は Promise<URLSearchParams> を返す。同一ティック内の呼び出しは同じ Promise 参照を返す。
オプション優先順位
高い順:
1. Call-level: setState({ lat: 42 }, { shallow: false }) 2. Parser-level: parseAsFloat.withOptions({ shallow: false }) 3. Hook-level: useQueryStates({...}, { history: 'push' })
URL Key Remapping (urlKeys)
コード内の変数名と URL のキー名を分離する:
const [{ latitude, longitude }, setCoordinates] = useQueryStates(
{
latitude: parseAsFloat.withDefault(45.18),
longitude: parseAsFloat.withDefault(5.72),
},
{
urlKeys: {
latitude: 'lat',
longitude: 'lng',
},
}
)
// URL: ?lat=45.18&lng=5.72
// コード: latitude, longitudeUrlKeys 型ヘルパーで再利用可能な定義:
import type { UrlKeys } from 'nuqs'
export const coordinatesParsers = {
latitude: parseAsFloat.withDefault(45.18),
longitude: parseAsFloat.withDefault(5.72),
}
export const coordinatesUrlKeys: UrlKeys<typeof coordinatesParsers> = {
latitude: 'lat',
longitude: 'lng',
}注意事項
urlKeysは v1.20.0 で導入。UrlKeys型ヘルパーは v2.3.0 で導入- TanStack Router の
validateSearchとの併用時はurlKeys非対応
関連
- useQueryState
- オプション
Options
nuqs の URL 更新動作を制御するオプション。フックレベル、パーサーレベル、呼び出しレベルで設定可能。
デフォルト動作
- クライアントのみ更新(サーバーリクエストなし)—
shallow: true - 履歴エントリを置換 —
history: 'replace' - スクロールなし —
scroll: false - ブラウザ適応スロットル(50ms; Safari 120ms)
オプション設定方法
// パーサーのビルダーパターン
const [state, setState] = useQueryState(
'foo',
parseAsString.withOptions({ history: 'push' })
)
// call-level(フック/パーサーのオプションを上書き)
setState('bar', { scroll: true })全オプション
history
| 型 | `'replace' \ |
| デフォルト | 'replace' |
'replace': 更新を単一の履歴エントリに圧縮(git squash のように)。 'push': 更新ごとに新しい履歴エントリを作成。ブラウザの戻るボタンで状態変更を戻れる。
注意: 戻るボタンの動作を壊すと UX が悪化する。'push' はタブ切替やモーダルなどナビゲーション的な体験にのみ使用する。shallow
| 型 | boolean |
| デフォルト | true |
true: クライアントのみの更新。ネットワークリクエストなし。 false: サーバーに通知。SSR フレームワークの loader/RSC を再実行する。
React Router / Remix では shallow: false で loader が再実行される。
React Router での補足: useOptimisticSearchParams
React Router の useSearchParams は shallow 更新を反映しない。代わりに nuqs が提供する useOptimisticSearchParams を使用する:
// React Router v7 の場合
import { useOptimisticSearchParams } from 'nuqs/adapters/react-router/v7'
// React Router v6 の場合
import { useOptimisticSearchParams } from 'nuqs/adapters/react-router/v6'
const searchParams = useOptimisticSearchParams()
// loader 実行を待たずに最新のパラメータを読める(read-only)scroll
| 型 | boolean |
| デフォルト | false |
true: 更新時にページ上部へスクロール。 false: スクロール位置を維持。
limitUrlUpdates(URL 更新のレート制限)
| 型 | `throttle(ms) \ |
| デフォルト | ブラウザ適応スロットル(50ms; Safari 120ms; 旧 Safari 320ms) |
hooks が返す state は常に即座に更新される。レート制限されるのは URL の書き換えとサーバーリクエストのみ。
Throttle
最初の更新を即座に発行し、以降は一定間隔でバッチ処理:
import { throttle } from 'nuqs'
useQueryState('foo', {
shallow: false,
limitUrlUpdates: throttle(1000),
})Debounce
値の変更が止まるまで URL 更新を遅延。検索入力やスライダーに最適:
import { debounce, defaultRateLimit } from 'nuqs'
const [search, setSearch] = useQueryState(
'q',
parseAsString.withDefault('').withOptions({ shallow: false })
)
<input
value={search}
onChange={(e) =>
setSearch(e.target.value, {
limitUrlUpdates: e.target.value === '' ? undefined : debounce(500),
})
}
onKeyPress={(e) => {
if (e.key === 'Enter') {
setSearch(e.target.value) // Enter で即座に反映
}
}}
/>デフォルトレート制限にリセット
import { defaultRateLimit } from 'nuqs'
setState('bar', { limitUrlUpdates: defaultRateLimit })- 50ms 未満の値は無視される
+Infinityで URL 更新を無効化(hooks の state 同期は維持)
clearOnDefault
| 型 | boolean |
| デフォルト | true(v2.0.0 で false → true に変更) |
true: state がデフォルト値と等しい場合、キーを URL から削除。 false: デフォルト値でもキーを URL に保持。
=== 参照等価で比較。カスタムパーサーでは eq 関数を提供する:
const dateParser = createParser({
parse: (value: string) => new Date(value.slice(0, 10)),
serialize: (date: Date) => date.toISOString().slice(0, 10),
eq: (a: Date, b: Date) => a.getTime() === b.getTime(),
})startTransition
| 型 | React.startTransition 関数 |
| 必須条件 | shallow: false |
サーバー再レンダリング(RSC)をトランジションでラップし、ローディング状態を取得:
const [isLoading, startTransition] = React.useTransition()
const [query, setQuery] = useQueryState(
'query',
parseAsString.withOptions({ startTransition, shallow: false })
)注意: nuqs v1 ではstartTransitionを渡すと自動でshallow: falseになった。v2+ では明示的に設定が必要。
オプション優先順位
高い順:
1. Call-level: setState('value', { history: 'push' }) — 最高優先 2. Hook/Parser-level: parseAsString.withOptions({ ... }) 3. Adapter defaults: <NuqsAdapter defaultOptions={{...}}> — 最低優先
グローバルデフォルト(<NuqsAdapter> v2.5.0+)
<NuqsAdapter
defaultOptions={{
shallow: false,
scroll: true,
clearOnDefault: false,
limitUrlUpdates: throttle(250),
}}
>
{children}
</NuqsAdapter>processUrlSearchParams(v2.6.0+)
パラメータマージ後、URL 更新前に実行されるミドルウェア:
<NuqsAdapter
processUrlSearchParams={(search) => {
search.sort() // キーをアルファベット順にソート
return search
}}
>
{children}
</NuqsAdapter>非推奨オプション
throttleMs: v2.5.0 で非推奨。{ throttleMs: 100 }→{ limitUrlUpdates: throttle(100) }に置換
関連
- useQueryState
- useQueryStates
options
| Name | Description | Path |
|---|---|---|
| Options | nuqs の URL 更新動作を制御するオプション。フックレベル、パーサーレベル、呼び出しレベルで設定可能。 | options.md |
Built-in Parsers
nuqs が提供する全ビルトインパーサー。useQueryState の第2引数に渡して型安全な値変換を行う。
一覧
| Parser | 戻り値型 | 説明 |
|---|---|---|
parseAsString | string | No-op パーサー。.withDefault() / .withOptions() ビルダー用 |
parseAsInteger | number | parseInt(value, 10) で整数に変換 |
parseAsFloat | number | parseFloat(value) で浮動小数点数に変換 |
parseAsHex | number | 16進数エンコードされた整数 |
parseAsIndex | number | 整数 + オフセット(URL では +1、parse 時に -1)。ページネーション用 |
parseAsBoolean | boolean | 真偽値 |
parseAsStringLiteral(values) | `'a' \ | 'b' \ |
parseAsNumberLiteral(values) | `1 \ | 2 \ |
parseAsStringEnum<E>(values) | E | 文字列 enum |
parseAsIsoDateTime | Date | ISO 8601 datetime |
parseAsIsoDate | Date | ISO 8601 date(時刻 00:00:00 UTC)。v2.1.0+ |
parseAsTimestamp | Date | Unix epoch からのミリ秒 |
parseAsArrayOf(parser, sep?) | T[] | カンマ区切り配列(セパレータ変更可) |
parseAsNativeArrayOf(parser) | T[] | ネイティブ URL 配列(?k=a&k=b)。v2.7.0+ |
parseAsJson(schema) | T | JSON + オプションバリデーション |
すべて nuqs からインポート:
import { parseAsString, parseAsInteger, parseAsFloat, /* ... */ } from 'nuqs'使用例
String
import { parseAsString } from 'nuqs'
// No-op パーサー。.withDefault() / .withOptions() のビルダーパターンに有用
export const searchParsers = {
q: parseAsString.withDefault('').withOptions({ shallow: false })
}Integer / Float
import { parseAsInteger, parseAsFloat } from 'nuqs'
useQueryState('page', parseAsInteger.withDefault(0))
useQueryState('zoom', parseAsFloat.withDefault(1.0))Hex
import { parseAsHex } from 'nuqs'
useQueryState('color', parseAsHex.withDefault(0x00))Index(ページネーション)
import { parseAsIndex } from 'nuqs'
const [pageIndex] = useQueryState('page', parseAsIndex.withDefault(0))
// pageIndex 0 → ?page=1
// pageIndex 1 → ?page=2Boolean
import { parseAsBoolean } from 'nuqs'
useQueryState('enabled', parseAsBoolean.withDefault(false))String Literals
import { parseAsStringLiteral, type inferParserType } from 'nuqs'
const sortOrder = ['asc', 'desc'] as const
const parser = parseAsStringLiteral(sortOrder)
type SortOrder = inferParserType<typeof parser> // 'asc' | 'desc'Number Literals
import { parseAsNumberLiteral } from 'nuqs'
const diceSides = [1, 2, 3, 4, 5, 6] as const
parseAsNumberLiteral(diceSides)String Enum
import { parseAsStringEnum } from 'nuqs'
enum Direction {
up = 'UP',
down = 'DOWN',
left = 'LEFT',
right = 'RIGHT',
}
parseAsStringEnum<Direction>(Object.values(Direction))
// URL の値は enum の value: ?direction=UPDates
import { parseAsIsoDateTime, parseAsIsoDate, parseAsTimestamp } from 'nuqs'
useQueryState('at', parseAsIsoDateTime) // ISO 8601 datetime
useQueryState('date', parseAsIsoDate) // ISO 8601 date, 00:00 UTC (v2.1.0+)
useQueryState('ts', parseAsTimestamp) // ms since Unix epochArray(カンマ区切り)
import { parseAsArrayOf, parseAsInteger } from 'nuqs'
parseAsArrayOf(parseAsInteger) // ?ids=1,2,3
parseAsArrayOf(parseAsInteger, ';') // カスタムセパレータ: ?ids=1;2;3Native Array(繰り返しキー)
import { parseAsNativeArrayOf, parseAsInteger } from 'nuqs'
const [ids] = useQueryState('id', parseAsNativeArrayOf(parseAsInteger))
// ?id=1&id=2&id=3 → [1, 2, 3]
// ビルトインデフォルト [] — null を扱う必要なしJSON(スキーマバリデーション付き)
import { parseAsJson } from 'nuqs'
import { z } from 'zod'
const schema = z.object({
pkg: z.string(),
version: z.number(),
worksWith: z.array(z.string()),
})
const [data, setData] = useQueryState('json', parseAsJson(schema))parseAsJson は以下を受け付ける:
- Standard Schema(Zod, ArkType, Valibot)
- カスタム同期バリデーション関数(throw または null を返す)
ビルダーメソッド
すべてのパーサーで使用可能:
// デフォルト値を設定(戻り値の型から null を除去)
parser.withDefault(value)
// オプションを設定
parser.withOptions({ history: 'push', shallow: false })
// チェーン可能
parseAsInteger.withDefault(0).withOptions({ history: 'push' })注意事項
parseAsStringはバリデーションなしで任意の値を受け付ける。制約付き文字列にはparseAsStringLiteralを使うparseAsNativeArrayOfはビルトインデフォルト[]を持つ。.withDefault()でカスタムデフォルトを設定可能- Next.js App Router のサーバー + クライアント共有コードでは
nuqs/serverからインポートする('use client'バンドルエラー回避)。他フレームワークではnuqsとnuqs/serverは互換
関連
- カスタムパーサー
- useQueryState
Custom Parsers
createParser でカスタムデータ型の URL パーサーを作成する。
シグネチャ
import { createParser } from 'nuqs'
const myParser = createParser<T>({
parse(queryValue: string): T | null,
serialize(value: T): string,
eq?(a: T, b: T): boolean,
})| プロパティ | 型 | 説明 |
|---|---|---|
parse | `(query: string) => T \ | null` |
serialize | (value: T) => string | 型付き値 → URL 文字列 |
eq | (a: T, b: T) => boolean | カスタム等価比較。clearOnDefault で使用。非プリミティブ型では必須 |
例: Star Rating パーサー
import { createParser } from 'nuqs'
const parseAsStarRating = createParser({
parse(queryValue) {
const inBetween = queryValue.split('★')
const isValid = inBetween.length > 1 && inBetween.every(s => s === '')
if (!isValid) return null
return Math.min(5, inBetween.length - 1)
},
serialize(value) {
return Array.from({ length: value }, () => '★').join('')
},
})等価関数 (eq)
オブジェクトや配列など、=== で比較できない型のデフォルト値検出に必要:
const parseAsSort = createParser({
parse(query) {
const [key = '', direction = ''] = query.split(':')
return { id: key, desc: direction === 'desc' }
},
serialize(value) {
return `${value.id}:${value.desc ? 'desc' : 'asc'}`
},
eq(a, b) {
return a.id === b.id && a.desc === b.desc
},
})
// ?sort=name:asc → { id: 'name', desc: false }Multi Parsers(繰り返しキー)
createMultiParser は繰り返しキー(?tag=a&tag=b)を扱う:
import { createMultiParser } from 'nuqs'
const parseAsFilters = createMultiParser({
parse(values: string[]) {
// values は同一キーの全値の配列
// パース結果を返す。無効なら null
},
serialize(value) {
// string[] を返す
return [...]
},
})SingleParser: キーの最初の出現のみ処理(デフォルト)MultiParser: キーの全出現を処理
注意事項
parseは無効な入力に対してnullを返すこと。throw しない- Lossy serializer に注意: シリアライザが精度を失う場合(例:
toFixed(4))、ページリロード時にデータが劣化する。URL がハイドレーション時の唯一の情報源であるため - カスタムパーサーでも
.withDefault()と.withOptions()のビルダーメソッドが使用可能
関連
- ビルトインパーサー
- オプション
Parsers
| Name | Description | Path |
|---|---|---|
| Built-in Parsers | nuqs が提供する全ビルトインパーサー。useQueryState の第2引数に渡して型安全な値変換を行う。 | built-in.md |
| Custom Parsers | createParser でカスタムデータ型の URL パーサーを作成する。 | custom.md |
Server
| Name | Description | Path |
|---|---|---|
| Server-side Usage | サーバーサイドで URL クエリパラメータを型安全に解析する API。 | server-side.md |
Server-side Usage
サーバーサイドで URL クエリパラメータを型安全に解析する API。
createLoader(v2.3.0+)
型安全なサーバーサイドパラメータ解析。パーサー定義を共有して、クライアントとサーバーで一貫した型を使用する。
シグネチャ
import { createLoader } from 'nuqs/server'
const loadSearchParams = createLoader(keyMap, options?)| 引数 | 型 | 説明 |
|---|---|---|
keyMap | Record<string, Parser> | キーとパーサーのマッピング |
options | object | 省略可 |
options.strict | boolean | 無効な値に対してエラーを throw する(デフォルト: false、v2.5.0+) |
基本例
import { parseAsFloat, parseAsString, createLoader } from 'nuqs/server'
// パーサー定義(クライアントと共有)
export const coordinatesSearchParams = {
latitude: parseAsFloat.withDefault(0),
longitude: parseAsFloat.withDefault(0),
}
export const loadSearchParams = createLoader(coordinatesSearchParams)フレームワーク別の使い方
React Router / Remix:
// loader 内
export async function loader({ request }: LoaderFunctionArgs) {
const { latitude, longitude } = loadSearchParams(request)
// または
const { latitude, longitude } = loadSearchParams(request.url)
return { latitude, longitude }
}Next.js App Router:
export default async function Page({ searchParams }) {
const { latitude, longitude } = await loadSearchParams(searchParams)
return <Map lat={latitude} lng={longitude} />
}受け付ける入力型
- 完全修飾 URL 文字列
- クエリ文字列(
?lat=45&lng=5) URLオブジェクトURLSearchParamsオブジェクトRequestオブジェクトRecord<string, string | string[] | undefined>- 上記いずれかの
Promise
Strict Mode(v2.5.0+)
デフォルトでは無効な値はデフォルト値または null を返す。strict mode ではエラーを throw:
loadSearchParams('?count=banana', { strict: true })
// Throws: [nuqs] Error while parsing query `banana` for key `count`createSearchParamsCache
深くネストされたサーバーコンポーネントで props drilling なしにパラメータにアクセスする。
注意: この API は React のcache関数に基づいており、主に Next.js App Router のサーバーコンポーネント向け。React Router / Remix ではcreateLoaderを使用する。
シグネチャ
import { createSearchParamsCache } from 'nuqs/server'
const searchParamsCache = createSearchParamsCache(keyMap, options?)| 引数 | 型 | 説明 |
|---|---|---|
keyMap | Record<string, Parser> | キーとパーサーのマッピング |
options | object | 省略可。strict / urlKeys を含む |
options.strict | boolean | 無効な値に対してエラーを throw する(デフォルト: false) |
options.urlKeys | Record<string, string> | 変数名と URL キー名のマッピング |
基本例
import { parseAsString, parseAsInteger, createSearchParamsCache } from 'nuqs/server'
export const searchParamsCache = createSearchParamsCache({
q: parseAsString.withDefault(''),
maxResults: parseAsInteger.withDefault(10),
})使い方(Next.js App Router)
// ページコンポーネントで .parse() を呼ぶ
export default async function Page({ searchParams }) {
searchParamsCache.parse(searchParams)
return <Results />
}
// 子コンポーネントで .get() または .all() でアクセス
function Results() {
const q = searchParamsCache.get('q')
const maxResults = searchParamsCache.get('maxResults')
// または
const { q, maxResults } = searchParamsCache.all()
return <div>...</div>
}メソッド
| メソッド | 説明 |
|---|---|
.parse(searchParams, options?) | パラメータを解析してキャッシュに格納。入力が Promise の場合は Promise を返す |
.get(key) | 単一キーの値を取得 |
.all() | 全キーの値をオブジェクトで取得 |
注意事項
createSearchParamsCacheは React のcache関数に基づく。現在のページレンダリング内でのみ有効。Next.js App Router のサーバーコンポーネント専用- React Router / Remix では
createSearchParamsCacheではなくcreateLoaderを loader 関数内で使い、requestまたはrequest.urlを渡す - Loader はデータのバリデーションを行わない。JSON オブジェクトや特定の制約には Zod 等のスキーマバリデーションを併用する
関連
- ビルトインパーサー
- オプション
Adapter Setup
Wrap the app with a framework-specific NuqsAdapter to enable query state hooks.
// Next.js App Router — 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 — 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>
)
}// React SPA (Vite) — src/main.tsx
import { NuqsAdapter } from 'nuqs/adapters/react'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<NuqsAdapter>
<App />
</NuqsAdapter>
</StrictMode>
)Notes
- Each framework has its own import path:
nuqs/adapters/next/app,nuqs/adapters/next/pages,nuqs/adapters/react,nuqs/adapters/remix,nuqs/adapters/react-router/v7,nuqs/adapters/tanstack-router - The adapter must wrap all components that use
useQueryStateoruseQueryStates - Global defaults can be set via
<NuqsAdapter defaultOptions={{ shallow: false, scroll: true }}>(v2.5.0+) - For testing, use
withNuqsTestingAdapterfromnuqs/adapters/testinginstead
Array Params
Store arrays in URL query parameters using parseAsArrayOf or parseAsNativeArrayOf.
'use client'
import {
useQueryState,
parseAsArrayOf,
parseAsString,
parseAsNativeArrayOf,
parseAsInteger,
} from 'nuqs'
export function TagFilter() {
// Comma-separated: ?tags=books,tech,science
const [tags, setTags] = useQueryState(
'tags',
parseAsArrayOf(parseAsString).withDefault([])
)
// Repeated keys: ?page=1&page=3&page=5
const [pages, setPages] = useQueryState(
'page',
parseAsNativeArrayOf(parseAsInteger).withDefault([])
)
const toggle = (tag: string) =>
setTags(prev =>
prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag]
)
return (
<div>
{['books', 'tech', 'science'].map(tag => (
<label key={tag}>
<input
type="checkbox"
checked={tags.includes(tag)}
onChange={() => toggle(tag)}
/>
{tag}
</label>
))}
</div>
)
}Notes
parseAsArrayOf(parser)serializes as a delimiter-separated string (default,); pass a custom separator as second argument:parseAsArrayOf(parseAsString, '|')parseAsNativeArrayOf(parser)uses repeated URL keys (?tag=a&tag=b), which is the standard URL array format- All built-in parsers can be used as the element parser inside
parseAsArrayOf/parseAsNativeArrayOf - Empty array and
nullare distinct: empty array ([]) keeps the key in the URL (unlessclearOnDefaultremoves it), whilenullalways removes the key
Basic Query State
Single URL query parameter synced with React state using useQueryState.
'use client'
import { useQueryState } from 'nuqs'
export function Demo() {
const [name, setName] = useQueryState('name')
return (
<>
<input value={name || ''} onChange={e => setName(e.target.value)} />
<button onClick={() => setName(null)}>Clear</button>
<p>Hello, {name || 'anonymous visitor'}!</p>
</>
)
}Notes
useQueryStatereturnsstring | nullby default;nullmeans the key is absent from the URL- Calling
setValue(null)removes the key from the URL entirely - Drop-in replacement for
React.useState— same tuple API - In Next.js App Router, add
'use client'directive since this is a client-side hook
Custom Parser
Create a type-safe parser for a custom data format using createParser.
import { createParser, useQueryState } from 'nuqs'
// Encodes sort state as "field:direction" in the URL (e.g., ?sort=name:asc)
const parseAsSort = createParser({
parse(query: string) {
const [key = '', direction = ''] = query.split(':')
if (!key) return null
return { id: key, desc: direction === 'desc' }
},
serialize(value: { id: string; desc: boolean }) {
return `${value.id}:${value.desc ? 'desc' : 'asc'}`
},
eq(a, b) {
return a.id === b.id && a.desc === b.desc
},
})
export function SortControl() {
const [sort, setSort] = useQueryState(
'sort',
parseAsSort.withDefault({ id: 'name', desc: false })
)
return (
<button onClick={() => setSort({ id: sort.id, desc: !sort.desc })}>
Sort by {sort.id} ({sort.desc ? 'desc' : 'asc'})
</button>
)
}Notes
parsemust returnnullfor invalid input — never throweqis required for non-primitive types soclearOnDefaultcan detect equality correctly- The parser gains
.withDefault()and.withOptions()builder methods automatically createMultiParserhandles repeated URL keys (?tag=a&tag=b) whereparsereceivesstring[]
Multiple Params Batch Update
Manage multiple query parameters atomically with useQueryStates; updates within the same event loop tick are batched into a single URL change.
'use client'
import { useQueryStates, parseAsFloat } from 'nuqs'
export function CoordinatesPicker() {
const [coordinates, setCoordinates] = useQueryStates(
{
lat: parseAsFloat.withDefault(45.18),
lng: parseAsFloat.withDefault(5.72),
},
{ history: 'push' }
)
const { lat, lng } = coordinates
return (
<>
<p>Lat: {lat}, Lng: {lng}</p>
<button
onClick={() =>
setCoordinates({
lat: Math.random() * 180 - 90,
lng: Math.random() * 360 - 180,
})
}
>
Randomize
</button>
<button onClick={() => setCoordinates(null)}>Reset</button>
</>
)
}Notes
- Both
latandlngupdate in a single history entry — no intermediate URL states setCoordinates(null)clears all managed keys without affecting other query params- The setter returns
Promise<URLSearchParams>—await setCoordinates(...)resolves after the URL is updated - Partial updates are supported:
setCoordinates({ lat: 42 })leaveslngunchanged
samples
| Name | Description | Path |
|---|---|---|
| Adapter Setup | Wrap the app with a framework-specific NuqsAdapter to enable query state hooks. | adapter-setup.md |
| Array Params | Store arrays in URL query parameters using parseAsArrayOf or parseAsNativeArrayOf. | array-params.md |
| Basic Query State | Single URL query parameter synced with React state using useQueryState. | basic-query-state.md |
| Custom Parser | Create a type-safe parser for a custom data format using createParser. | custom-parser.md |
| Multiple Params Batch Update | Manage multiple query parameters atomically with useQueryStates; updates within the same event loop tick are batched into a single URL change. | multiple-params-batch-update.md |
| Search Filter with Debounce | Debounce URL updates for search input so the URL only changes after the user stops typing. | search-filter-debounce.md |
| Search Params Cache | Avoid prop drilling in deeply nested Next.js App Router server components using createSearchParamsCache. | search-params-cache.md |
| Server-Side Parsing | Parse URL search params on the server using createLoader (shared parsers between client and server). | server-side-parsing.md |
| Testing | Unit-test components that use useQueryState with withNuqsTestingAdapter. | testing.md |
| Typed State with Parsers | Use built-in parsers to get typed values (number, boolean, etc.) from URL query parameters. | typed-state-with-parsers.md |
| URL Key Remapping | Use descriptive variable names in code while keeping URL keys short with the urlKeys option. | url-key-remapping.md |
Search Filter with Debounce
Debounce URL updates for search input so the URL only changes after the user stops typing.
'use client'
import { useQueryState, parseAsString, debounce } from 'nuqs'
export function SearchFilter() {
const [search, setSearch] = useQueryState(
'q',
parseAsString.withDefault('').withOptions({ shallow: false })
)
return (
<input
type="search"
value={search}
placeholder="Search..."
onChange={e =>
setSearch(e.target.value, {
limitUrlUpdates: e.target.value === '' ? undefined : debounce(500),
})
}
onKeyDown={e => {
if (e.key === 'Enter') {
// Flush immediately on Enter
setSearch((e.target as HTMLInputElement).value)
}
}}
/>
)
}Notes
- The hook's state updates instantly for a responsive UI; only URL writes are debounced
shallow: falsetriggers server-side re-fetch (RSC / loader) after the debounce settles- Clearing the input (empty string) skips debounce so the URL clears immediately
- Use
throttle(ms)instead ofdebounce(ms)for sliders that emit frequent events at a steady rate
Search Params Cache
Avoid prop drilling in deeply nested Next.js App Router server components using createSearchParamsCache.
// lib/search-params.ts
import { parseAsString, parseAsInteger, createSearchParamsCache } from 'nuqs/server'
export const searchParamsCache = createSearchParamsCache({
q: parseAsString.withDefault(''),
maxResults: parseAsInteger.withDefault(10),
})// app/search/page.tsx — parse once at the page boundary
import { searchParamsCache } from '@/lib/search-params'
import type { SearchParams } from 'nuqs/server'
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<SearchParams>
}) {
await searchParamsCache.parse(searchParams)
return <Results />
}// app/search/results.tsx — access anywhere in the tree without props
import { searchParamsCache } from '@/lib/search-params'
export function Results() {
const q = searchParamsCache.get('q')
const maxResults = searchParamsCache.get('maxResults')
return <div>Showing up to {maxResults} results for "{q}"</div>
}Notes
createSearchParamsCacherelies on React'scachefunction — valid only within the current page render- Call
.parse()exactly once per request, at the highest server component in the tree .all()returns all cached values as an object:const { q, maxResults } = searchParamsCache.all()- For React Router / Remix, use
createLoaderinside aloaderfunction instead
Server-Side Parsing
Parse URL search params on the server using createLoader (shared parsers between client and server).
// lib/search-params.ts — shared parser definitions
import { parseAsString, parseAsInteger, createLoader } from 'nuqs/server'
export const searchParsers = {
q: parseAsString.withDefault(''),
page: parseAsInteger.withDefault(1),
}
export const loadSearchParams = createLoader(searchParsers)// app/products/page.tsx — Next.js App Router server component
import { loadSearchParams } from '@/lib/search-params'
import type { SearchParams } from 'nuqs/server'
type Props = { searchParams: Promise<SearchParams> }
export default async function ProductsPage({ searchParams }: Props) {
const { q, page } = await loadSearchParams(searchParams)
const results = await fetchProducts({ query: q, page })
return <ProductList items={results} />
}Notes
- Import from
'nuqs/server'(not'nuqs') to avoid bundling client-only code in server components createLoaderaccepts strings,URL,URLSearchParams,Request, or theirPromisewrappers- Parser definitions in a shared file keep client and server types in sync automatically
- For strict validation (throw on invalid values instead of falling back), pass
{ strict: true }as the second argument to the loader call
Testing
Unit-test components that use useQueryState with withNuqsTestingAdapter.
// counter.test.tsx (Vitest + React Testing Library)
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
import { CounterButton } from './counter'
it('increments the counter and updates the URL', async () => {
const onUrlUpdate = vi.fn()
render(<CounterButton />, {
wrapper: withNuqsTestingAdapter({
searchParams: '?count=42',
onUrlUpdate,
}),
})
await userEvent.click(screen.getByRole('button'))
expect(onUrlUpdate).toHaveBeenCalledOnce()
const { searchParams } = onUrlUpdate.mock.calls[0][0]
expect(searchParams.get('count')).toBe('43')
})// Hook testing with renderHook
import { renderHook, act } from '@testing-library/react'
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
import { useMyHook } from './use-my-hook'
it('initializes from searchParams', () => {
const { result } = renderHook(() => useMyHook(), {
wrapper: withNuqsTestingAdapter({ searchParams: { count: '42' } }),
})
expect(result.current.count).toBe(42)
})Notes
withNuqsTestingAdapteracceptssearchParamsas a query string,URLSearchParams, or a plain record objectonUrlUpdatereceives{ searchParams: URLSearchParams, queryString: string }on every URL change- By default, the adapter is stateless (each update does not persist) — set
hasMemory: trueto simulate real navigation state - nuqs v2 is ESM-only; Jest requires
extensionsToTreatAsEsmand--experimental-vm-modulesflags
Typed State with Parsers
Use built-in parsers to get typed values (number, boolean, etc.) from URL query parameters.
'use client'
import { useQueryState, parseAsInteger, parseAsBoolean, parseAsFloat } from 'nuqs'
export function TypedDemo() {
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))
// count: number (never null)
const [enabled, setEnabled] = useQueryState('enabled', parseAsBoolean.withDefault(false))
// enabled: boolean
const [ratio, setRatio] = useQueryState('ratio', parseAsFloat.withDefault(1.0))
// ratio: number
return (
<>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<button onClick={() => setEnabled(v => !v)}>Enabled: {String(enabled)}</button>
<input
type="range"
min={0}
max={2}
step={0.1}
value={ratio}
onChange={e => setRatio(parseFloat(e.target.value))}
/>
</>
)
}Notes
.withDefault(value)eliminatesnullfrom the return type- Invalid URL values (e.g.,
?count=banana) fall back to the default value silently - Available built-in parsers:
parseAsInteger,parseAsFloat,parseAsBoolean,parseAsString,parseAsHex,parseAsIndex,parseAsIsoDateTime,parseAsIsoDate,parseAsTimestamp parseAsIndexadds a+1offset for display (useful for 1-based pagination in URLs)
URL Key Remapping
Use descriptive variable names in code while keeping URL keys short with the urlKeys option.
'use client'
import { useQueryStates, parseAsFloat } from 'nuqs'
import type { UrlKeys } from 'nuqs'
// Reusable parser definitions
export const coordinatesParsers = {
latitude: parseAsFloat.withDefault(45.18),
longitude: parseAsFloat.withDefault(5.72),
}
// Map readable names → short URL keys
export const coordinatesUrlKeys: UrlKeys<typeof coordinatesParsers> = {
latitude: 'lat',
longitude: 'lng',
}
export function CoordinatesDisplay() {
const [{ latitude, longitude }, setCoordinates] = useQueryStates(
coordinatesParsers,
{ urlKeys: coordinatesUrlKeys }
)
// URL: ?lat=45.18&lng=5.72
// Code uses: latitude, longitude
return (
<p>
Position: {latitude.toFixed(4)}, {longitude.toFixed(4)}
</p>
)
}Notes
urlKeysseparates internal naming from URL representation without changing runtime behavior- Exporting
UrlKeysalongside parsers makes the mapping reusable across components urlKeysis not supported when using TanStack Router'svalidateSearchintegration- Introduced in nuqs v1.20.0; the
UrlKeystype helper was added in v2.3.0
Debug
nuqs のデバッグ・トラブルシューティング用コマンド。
ブラウザコンソールでのデバッグログ有効化
ブラウザの開発者ツールコンソールで実行する。URL 更新のログが出力される。
localStorage.setItem('debug', 'nuqs')デバッグログの無効化
localStorage.removeItem('debug')v1 から v2 へのデバッグキー移行
nuqs v1(next-usequerystate)から v2 へ移行する際に、デバッグキーを更新する。
if (localStorage.debug) {
localStorage.debug = localStorage.debug.replace('next-usequerystate', 'nuqs')
}Install
nuqs パッケージのインストールコマンド集。
npm でのインストール
npm install nuqspnpm でのインストール
pnpm add nuqsyarn でのインストール
yarn add nuqsbun でのインストール
bun add nuqs旧バージョン(nuqs v1)のインストール
Next.js の古いバージョンを使用する場合は v1 系を指定する。
npm install nuqs@^1pnpm add nuqs@^1scripts
| Name | Description | Path |
|---|---|---|
| Debug | nuqs のデバッグ・トラブルシューティング用コマンド。 | debug.md |
| Install | nuqs パッケージのインストールコマンド集。 | install.md |
| Setup | フレームワーク別の NuqsAdapter セットアップ手順。インストール後に行う初期設定。 | setup.md |
| Testing | nuqs を使ったコンポーネント・フック・パーサーのテスト設定とコマンド。 | testing.md |
Setup
フレームワーク別の NuqsAdapter セットアップ手順。インストール後に行う初期設定。
Next.js App Router のセットアップ
app/layout.tsx でルートレイアウトを NuqsAdapter でラップする。
import { NuqsAdapter } from 'nuqs/adapters/next/app'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<NuqsAdapter>{children}</NuqsAdapter>
</body>
</html>
)
}Next.js Pages Router のセットアップ
_app.tsx でページコンポーネントを NuqsAdapter でラップする。
import { NuqsAdapter } from 'nuqs/adapters/next/pages'
import type { AppProps } from 'next/app'
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<NuqsAdapter>
<Component {...pageProps} />
</NuqsAdapter>
)
}Next.js 統合アダプター(App + Pages 両対応)のセットアップ
App Router と Pages Router を併用するアプリ向け。
import { NuqsAdapter } from 'nuqs/adapters/next'React SPA(Vite 等)のセットアップ
main.tsx でルートを NuqsAdapter でラップする。
import { NuqsAdapter } from 'nuqs/adapters/react'
import { createRoot } from 'react-dom/client'
createRoot(document.getElementById('root')!).render(
<NuqsAdapter>
<App />
</NuqsAdapter>
)フルページナビゲーションオプション付き(v2.4.0+):
<NuqsAdapter fullPageNavigationOnShallowFalseUpdates>
<App />
</NuqsAdapter>Remix のセットアップ
app/root.tsx で Outlet を NuqsAdapter でラップする。
import { NuqsAdapter } from 'nuqs/adapters/remix'
import { Outlet } from '@remix-run/react'
export default function App() {
return (
<NuqsAdapter>
<Outlet />
</NuqsAdapter>
)
}React Router v6 のセットアップ
import { NuqsAdapter } from 'nuqs/adapters/react-router/v6'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
const router = createBrowserRouter([{ path: '/', element: <App /> }])
export function ReactRouter() {
return (
<NuqsAdapter>
<RouterProvider router={router} />
</NuqsAdapter>
)
}React Router v7 のセットアップ
import { NuqsAdapter } from 'nuqs/adapters/react-router/v7'
import { Outlet } from 'react-router'
export default function App() {
return (
<NuqsAdapter>
<Outlet />
</NuqsAdapter>
)
}TanStack Router のセットアップ
ルートルートのコンポーネントを NuqsAdapter でラップする。
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router'
import { Outlet, createRootRoute } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () => (
<NuqsAdapter>
<Outlet />
</NuqsAdapter>
),
})Testing
nuqs を使ったコンポーネント・フック・パーサーのテスト設定とコマンド。
テストアダプターのインポート
import { withNuqsTestingAdapter, NuqsTestingAdapter } from 'nuqs/adapters/testing'フックのテスト(React Testing Library + renderHook)
renderHook の wrapper に withNuqsTestingAdapter を渡す。
import { renderHook } from '@testing-library/react'
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
const { result } = renderHook(() => useTheHookToTest(), {
wrapper: withNuqsTestingAdapter({
searchParams: { count: '42' },
}),
})コンポーネントのテスト(Vitest + Testing Library)
render の wrapper に withNuqsTestingAdapter を渡し、URL 更新を onUrlUpdate で検証する。
import { render } from '@testing-library/react'
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing'
import { vi } from 'vitest'
const onUrlUpdate = vi.fn()
render(<CounterButton />, {
wrapper: withNuqsTestingAdapter({
searchParams: '?count=42',
onUrlUpdate,
}),
})クエリ文字列形式での初期パラメーター指定
withNuqsTestingAdapter({ searchParams: '?q=hello&limit=10' })URLSearchParams 形式での初期パラメーター指定
withNuqsTestingAdapter({ searchParams: new URLSearchParams('?q=hello&limit=10') })オブジェクト形式での初期パラメーター指定
withNuqsTestingAdapter({ searchParams: { q: 'hello', limit: '10' } })カスタムパーサーのテスト
パーサーの双射性(parse ↔ serialize の往復整合)を検証するユーティリティを使う。
import {
isParserBijective,
testParseThenSerialize,
testSerializeThenParse,
} from 'nuqs/testing'
expect(isParserBijective(parseAsInteger, '42', 42)).toBe(true)
expect(testParseThenSerialize(parseAsInteger, '42')).toBe(true)
expect(testSerializeThenParse(parseAsInteger, 42)).toBe(true)Jest の ESM 対応設定
jest.config.ts に以下を追加する。
const config: Config = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transform: {},
}package.json の test スクリプト(Windows は cross-env を使う):
{
"scripts": {
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest"
}
}