型付きの状態、住む場所は URL
useUrlState は、自分自身をクエリ文字列に書き込む React の状態です。オブジェクト、配列、日付は型を保ち、あらゆる状態は共有可能なリンクになり、リロード後も維持されます。プロバイダーもボイラープレートも不要。
- ~2 KB gzip 圧縮
- 依存関係ゼロ
- TypeScript ファースト
- Next.js / react-router / Remix
- MIT
npm i state-in-urlstate-in-url vs nuqs
nuqs の代替をお探しですか?どちらも型付きの状態をクエリ文字列に保存しますが、必要な設定量と、値として扱えるものが異なります。
| 項目 | state-in-url | nuqs |
|---|---|---|
| セットアップ | 不要 — hook を import するだけ | アダプターコンポーネントでアプリをラップ |
| 状態の形 | React.useState のような型付きオブジェクト1つ | キーごとの値、それぞれにパーサーを宣言 |
| コンポーネント間の再利用 | hook を一度包むだけ — 全コンポーネントが状態を共有、props 不要 | パーサー群を包む hook は自分で抽出 |
| ネストしたオブジェクトと配列 | 標準対応 — 構造と型を保持 | JSON パーサーに加えて自前のバリデーターが必要 |
| 日付 | 自動的に保持 | 組み込みパーサーをキーごとに宣言 |
| サイズ(全体 import) | 約 2.9 KB gzip | 約 6.7 KB gzip |
| ランタイム依存 | なし | 1つ |
| ルーター | Next.js、React Router v6/v7、Remix、素の JS 用ヘルパー | Next.js、React Router、Remix、TanStack Router、素の React |
サイズはライブラリ全体の import を esbuild minify + gzip で計測(2026年8月、nuqs 2.10.1 と比較)。
nuqs も優れたライブラリです。値ごとに読みやすいクエリパラメータが欲しいとき、TanStack Router を使っているときは nuqs を。型付きオブジェクトを丸ごと URL に、設定ゼロで入れたいなら state-in-url を選んでください。
同じ機能を、両方で
フィルターパネル:検索文字列、ページ番号、タグ一覧、日付。nuqs はキーごとにパーサーを宣言しルートにアダプターを配線し、state-in-url はオブジェクトを受け取り、再利用できる1つの hook に包みます。
// app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app';
export default function RootLayout({ children }) {
return (
<html>
<body>
<NuqsAdapter>{children}</NuqsAdapter>
</body>
</html>
);
}'use client';
import {
useQueryStates,
parseAsString,
parseAsInteger,
parseAsArrayOf,
parseAsIsoDateTime,
} from 'nuqs';
export const Filters = () => {
const [filters, setFilters] = useQueryStates({
q: parseAsString.withDefault(''),
page: parseAsInteger.withDefault(1),
tags: parseAsArrayOf(parseAsString).withDefault([]),
since: parseAsIsoDateTime,
});
return (
<input
value={filters.q}
onChange={(ev) => setFilters({ q: ev.target.value, page: 1 })}
/>
);
};'use client';
import { useUrlState } from 'state-in-url/next';
export const filters = {
q: '',
page: 1,
tags: [] as string[],
since: undefined as Date | undefined,
};
// One reusable hook = the whole API for this feature
export const useFilters = () => useUrlState(filters);
export const SearchBox = () => {
const { urlState, setUrl } = useFilters();
return (
<input
value={urlState.q}
onChange={(ev) => setUrl({ q: ev.target.value, page: 1 })}
/>
);
};
export const ActiveTags = () => {
// Same state, another component - no props, no context
const { urlState } = useFilters();
return <>{urlState.tags.join(', ')}</>; // tags is still string[]
};このカスタム hook がその機能の API のすべてです。呼び出したどのコンポーネントも同じ型付き状態を共有します — タグ一覧は配列のまま、日付は本物の Date オブジェクトで返ります。props も context も、キーごとの配線も不要です。
セットアップとボイラープレート
nuqs はアプリを包むアダプターコンポーネント経由でルーターにつながり、状態ごとにパーサーを宣言します。state-in-url はルーターごとに hook を用意 — 合うものを import し、デフォルト状態オブジェクトを渡せば完了。何も包みません。
Next.js・SSR・プリレンダリング
App Router では、state-in-url は決して useSearchParamsを呼ばないため、使うコンポーネントに Suspense 境界は不要で、ページはプリレンダリングされ続けます — PPR も含めて。サーバーコンポーネントは、そのまま転送された searchParams prop から同じ状態を読めます。
nuqs からの移行
移行はほぼ機械的です:1つの機能のキーを1つのデフォルト状態オブジェクトに集め、パーサー宣言を外し(型付きの値が同じ情報を運びます)、キーごとのセッターを partial を受け取る1つのセッターに置き換えます。トップレベルの各フィールドは引き続き自分のクエリパラメータに対応します。
ほかの選択肢との比較
nuqs だけが代替ではありません。同じ仕事 — クエリ文字列に型付き状態を置く — はルーター組み込み機能や古参ライブラリでもでき、それぞれにトレードオフがあります。
| ライブラリ | セットアップ | ネストしたオブジェクトと日付 | サイズ | 選ぶべき場面 |
|---|---|---|---|---|
| state-in-url | 不要 — hook を import | 型ごと自動で保持 | 約 2.9 KB gzip・依存ゼロ | Next.js・React Router・Remix で設定ゼロの型付きオブジェクトが欲しい |
| nuqs | アダプターコンポーネント、キーごとのパーサー | JSON パーサーに自前のバリデーター | 約 6.7 KB gzip・依存1つ | 値ごとに読みやすいクエリパラメータが欲しい |
| TanStack Router | 各ルートに validateSearch | オブジェクトと配列は JSON-first、日付は自前のシリアライズ | ルーターに内蔵 | TanStack Router を使っている — 内蔵機能を |
| use-query-params | Provider とルーターアダプター、パラメータごとの設定 | JSON パラメータ型経由、型は緩い | 約 4.4 KB gzip + serialize-query-params | 既にこれで構築されたコードベース |
| useSearchParams | 不要 — ルーターに内蔵 | 文字列のみ — パース・型・デフォルトはすべて自前 | 0 KB | フラットな文字列パラメータが1〜2個だけ |
よくある質問
- state-in-url は nuqs の良い代替ですか?
- はい。型付きオブジェクトを丸ごと URL に、設定ゼロで入れたい場合に向きます:アダプターコンポーネントもキーごとのパーサーも不要で、ネストしたオブジェクトと日付は自動で保持されます。値ごとに読みやすいクエリパラメータが欲しい場合や TanStack Router を使っている場合は、引き続き nuqs が良い選択です。
- state-in-url と nuqs はどちらが小さいですか?
- 2026年8月に esbuild(minify + gzip、ライブラリ全体の import)で計測:state-in-url は約 2.9 KB・ランタイム依存ゼロ、nuqs 2.10.1 は約 6.7 KB・依存1つ。一部だけの import ならどちらも小さくなります。
- state-in-url にアダプターやプロバイダーは必要ですか?
- 不要です。ルーターごとに専用のエントリーポイントがあり、対応する hook を import してデフォルト状態オブジェクトを渡せば動きます。アプリを包むアダプターコンポーネントも、設定すべき context プロバイダーもありません。
- nuqs から state-in-url への移行は大変ですか?
- ほぼ機械的です:1つの機能のキーを1つのデフォルト状態オブジェクトに集め、パーサー宣言を外し、キーごとのセッターを partial を受け取る1つのセッターに置き換えます。トップレベルの各フィールドは引き続き自分のクエリパラメータに対応します。
- TanStack Router の search params は?
- TanStack Router を使っているなら、備え付けのものを使ってください:ルートごとの validateSearch で検証する JSON-first の search params です。state-in-url と nuqs が意味を持つのは、型付き search params が組み込まれていない Next.js・React Router・Remix の場合です。
