型付きの状態、住む場所は URL

useUrlState は、自分自身をクエリ文字列に書き込む React の状態です。オブジェクト、配列、日付は型を保ち、あらゆる状態は共有可能なリンクになり、リロード後も維持されます。プロバイダーもボイラープレートも不要。

  • ~2 KB gzip 圧縮
  • 依存関係ゼロ
  • TypeScript ファースト
  • Next.js / react-router / Remix
  • MIT
npm i state-in-url

state-in-url vs nuqs

nuqs の代替をお探しですか?どちらも型付きの状態をクエリ文字列に保存しますが、必要な設定量と、値として扱えるものが異なります。

項目state-in-urlnuqs
セットアップ不要 — 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 (nuqs)
// app/layout.tsx
import { NuqsAdapter } from 'nuqs/adapters/next/app';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  );
}
filters.tsx (nuqs)
'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 })}
    />
  );
};
filters.tsx (state-in-url)
'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-paramsProvider とルーターアダプター、パラメータごとの設定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 の場合です。