はじめに
Reactで検索フィルターやページネーションを実装するとき、URLのクエリパラメータと状態を同期させたいことってありますよね。
「この検索結果のURLを共有したい」とか「ブラウザバックで前の状態に戻りたい」とか、ユーザー目線で考えると当然の要望なんですが、これを自前で実装しようとすると意外と面倒なんですよ。
そこで今回紹介したいのが Nuqs というライブラリ。これ、URLのクエリパラメータをまるでuseStateのように扱えるようになるんです。個人的にはかなり感動したので、詳しく紹介していきます。
Nuqsとは
Nuqsは「Type-safe search params state manager for React」というキャッチフレーズの通り、URLのクエリパラメータを型安全に状態管理できるライブラリです。
- GitHub Stars: 9,500以上
- 月間ダウンロード数: 400万以上
- バンドルサイズ: 約6kB(gzip)
正直なところ、このスター数とダウンロード数を見て「なんで今まで知らなかったんだ」と思いました。
特徴・メリット
useStateライクなAPI
一番の特徴は、useStateとほぼ同じ感覚で使えること。学習コストがほぼゼロなんですよね。
// 普通のuseState
const [count, setCount] = useState(0)
// Nuqsの場合
const [count, setCount] = useQueryState('count', parseAsInteger.withDefault(0))
違いは第一引数にクエリパラメータ名を指定することと、パーサーを渡すことくらい。これでURLの?count=5みたいなパラメータと自動で同期してくれます。
型安全
TypeScriptとの相性が抜群です。パーサーを指定することで、stringだけじゃなくnumberやboolean、配列なども型安全に扱えます。
import { parseAsInteger, parseAsBoolean, parseAsArrayOf } from 'nuqs'
// 数値として扱う
const [page, setPage] = useQueryState('page', parseAsInteger)
// 真偽値として扱う
const [darkMode, setDarkMode] = useQueryState('dark', parseAsBoolean)
// 配列として扱う
const [selectedIds, setSelectedIds] = useQueryState(
'ids',
parseAsArrayOf(parseAsInteger)
)
これ、意外とありがたいんですよ。クエリパラメータって基本的に文字列なので、毎回パースするコードを書くのが地味に面倒だったんです。
マルチフレームワーク対応
Next.js(App Router / Pages Router)はもちろん、Remix、React Router、TanStack Routerなど主要なフレームワークに対応しています。プロジェクトを跨いでも同じ書き方ができるのは嬉しいですね。
ブラウザ履歴との連携
デフォルトではhistory.replaceState(URLを置き換え)ですが、history.pushState(履歴に追加)に切り替えることもできます。つまり、ブラウザの戻るボタンで前の状態に戻れるようになります。
インストール方法
お好みのパッケージマネージャーでインストールできます。
npm install nuqs
# または
yarn add nuqs
# または
pnpm add nuqs
基本的な使い方
Next.js App Routerでの設定
まず、ルートレイアウトでNuqsAdapterをラップします。
// 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>
)
}
シンプルな例
検索ボックスの値をURLに同期させる例です。
'use client'
import { useQueryState } from 'nuqs'
export default function SearchBox() {
const [query, setQuery] = useQueryState('q', { defaultValue: '' })
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value || null)}
placeholder="検索..."
/>
)
}
入力すると自動的にURLが?q=検索ワードのように変わります。ページをリロードしても状態が復元されるし、このURLを共有すれば同じ検索結果を見せられます。
複数のパラメータを扱う
useQueryStatesを使えば、複数のパラメータをまとめて管理できます。
'use client'
import { useQueryStates, parseAsInteger, parseAsString } from 'nuqs'
export default function ProductFilter() {
const [filters, setFilters] = useQueryStates({
category: parseAsString.withDefault('all'),
minPrice: parseAsInteger.withDefault(0),
maxPrice: parseAsInteger.withDefault(100000),
page: parseAsInteger.withDefault(1),
})
return (
<div>
<select
value={filters.category}
onChange={(e) => setFilters({ category: e.target.value })}
>
<option value="all">すべて</option>
<option value="electronics">家電</option>
<option value="clothing">衣類</option>
</select>
<input
type="number"
value={filters.minPrice}
onChange={(e) => setFilters({ minPrice: Number(e.target.value) })}
/>
<button onClick={() => setFilters({ page: filters.page + 1 })}>
次のページ
</button>
</div>
)
}
実践的なユースケース
ECサイトの商品一覧フィルター
個人的に一番使いたくなるのがこのパターン。カテゴリ、価格帯、ソート順、ページネーションなど、複数の条件を組み合わせた検索結果をURLで共有できるようになります。
「この条件で探してる商品、見てみて」ってURLを送れるのは、ユーザー体験としてかなり良いですよね。
ダッシュボードの表示設定
期間選択やグラフの表示モードなど、ダッシュボードの設定をURLに保持しておくと便利です。チームでデータを共有するときに「この期間のこのグラフ見て」ってURLを送るだけで済みます。
フォームのウィザード
ステップ形式のフォームで現在のステップをURLに保持しておけば、途中でブラウザを閉じても続きから再開できます。これ、ユーザーからするとかなりありがたい機能なんですよね。
タブやモーダルの状態管理
タブの選択状態やモーダルの開閉状態をURLに持たせることで、直リンクでその状態を再現できます。「設定画面の○○タブを開いて」っていう説明が不要になります。
まとめ
Nuqsを使うと、URLクエリパラメータの管理が劇的に楽になります。
useStateと同じ感覚で使える- 型安全にパラメータを扱える
- シェア可能なURLが簡単に作れる
- ブラウザバックにも対応できる
正直なところ、「なんで今まで自前で実装してたんだろう」という気持ちになりました。バンドルサイズも6kB程度と軽量なので、導入のハードルも低いです。
検索フィルターやページネーションを実装する機会がある方は、ぜひ試してみてください。QOL上がりますよ。