型付きの状態、住む場所は URL
useUrlState は、自分自身をクエリ文字列に書き込む React の状態です。オブジェクト、配列、日付は型を保ち、あらゆる状態は共有可能なリンクになり、リロード後も維持されます。プロバイダーもボイラープレートも不要。
- ~2 KB gzip 圧縮
- 依存関係ゼロ
- TypeScript ファースト
- Next.js / react-router / Remix
- MIT
npm i state-in-urluseUrlState — ライブで試す: remix.js
下に入力してください。URL が点灯するのを見てください
URL から読み取ります。props も context も不要で、型と構造は保持されます
{name: stringage: undefinedundefinedagree_to_terms: falsebooleantags: []}
同じ API、3 つのルーター
クイックスタート
export const form: Form = {
name: '',
age: undefined,
agree_to_terms: false,
tags: [],
};
// use `Type` not `Interface`!
type Form = {
name: string;
age?: number;
agree_to_terms: boolean;
tags: { id: string; value: { text: string; time: Date } }[];
};import { useUrlState } from 'state-in-url/remix';
import { form } from './form';
// One hook per feature - the whole API for this state
export const useFormState = () => useUrlState(form);import { useFormState } from './useFormState';
export const ComponentA = () => {
// see docs for all possible params https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState
const { urlState, setUrl, setState } = useFormState();
return <>
<input
id="name"
value={urlState.name}
onChange={(ev) => setUrl({ name: ev.target.value })}
/>
// OR can update state immediately but sync change to url as needed
<input
value={urlState.name}
onChange={(ev) => { setState(curr => ({ ...curr, name: ev.target.value })) }}
onBlur={() => setUrl()}
/>
<button onClick={() => setUrl((curr, initial) => initial)}>
Reset
</button>
</>
};import { useFormState } from './useFormState';
export const ComponentB = () => {
// same state as ComponentA - no props, no context
const { urlState } = useFormState();
// will be defaultValue from `form` if not in url, no need to check
return <div>name: {urlState.name}</div>
};import React from 'react';
import { useUrlState } from 'state-in-url/remix';
import { form } from './form';
export const useFormState = () => {
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form);
// first navigation will push new history entry
// all following will just replace that entry
// this way will have history with only 2 entries - ['/url', '/url?key=param']
const replace = React.useRef(false);
const setUrl = React.useCallback((
state: Parameters<typeof setUrlBase>[0],
opts?: Parameters<typeof setUrlBase>[1]
) => {
setUrlBase(state, { replace: replace.current, ...opts });
replace.current = true;
}, [setUrlBase]);
return { urlState, setUrl, resetUrl: reset };
};state-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 を選んでください。
なぜ state-in-url なのか?
URL 状態ライブラリは存在しますが、多くはセットアップが面倒だったり、保存できるものが限られていたりします。 state-in-url は「ただ動く」ことを目指しています。 React.useState を映す API で、URL をストアにします。
ボイラープレートなしで状態を保存し、ディープリンクを構築し、関連のないクライアントコンポーネント間でデータを共有できます。プロバイダーは不要です。構造と型は端から端まで保持されます。 Date が入り、 Date が出ます。
テストファーストで構築されており、ユニットテストとクロスブラウザの e2e スイートがすべてのコミットで実行されます。
Next.js: Suspense 境界は不要
このフックは useSearchParams を呼び出しません。そのため、このフックを使うコンポーネントは Suspense でラップする必要がなく、ページがプリレンダリングから除外されることもありません。PPR と cacheComponents も含みます。URL を直接読み取り、その後の変更をすべて追跡します。たとえば、 history.pushState を、その存在を知らないコードから呼ばれた場合も追跡します。
Next.js や react-router を使っていませんか?
この encodeState / decodeState ヘルパーは、どのフレームワークでもピュア JS でも動きます。フックはその上に載せた便利レイヤーです。
ぜひ GitHub ページ をご覧ください。スターをいただけると励みになります。
他の開発者と共有

