トグルホールディングスにグループインしたリーウェイズのdendeです。
現在は主にNext.jsで、自社プロダクトのリプレイスを推進しています。
最近知ったnuqsというライブラリが便利だったので紹介します。
nuqsとは
URLのクエリパラメータをReactの状態管理のように、リアクティブかつ型安全に管理できるようになる便利ライブラリです。
useStateのような直感的なAPIを提供しながら、クエリパラメータのパースやシリアライズを型安全に行うことができます。
本記事では、その導入から簡単な使い方までを解説します。
nuqsの良さげな点
- 軽量かつモダン: gzip圧縮時で約6KBと非常に軽量であり、React 19やNext.jsのApp Routerに最適化されています
- ゼロ依存のため、プロダクトに与える負荷は極めて小さく、導入コストが低い
-
useSearchParamsで指定した値をパーサーで型安全に扱え、コード量も抑えられるため可読性が上がる
インストール方法
まず、npmなどのパッケージマネージャを使用してライブラリをインストールします。
npm install nuqs
アダプターの設定
Next.jsのApp Router環境で使用するには、プロジェクトのルートレイアウト(src/app/layout.tsx)で、子要素を <NuqsAdapter> でラップする必要があります。これにより、アプリ全体でnuqsのコンテキストが有効になります。
// src/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>
)
}
基本的な使い方
nuqsは、useQueryState というフックを使用して状態を管理します。このフックはクライアントコンポーネント内でのみ使用可能なため、ファイルの冒頭に "use client" ディレクティブが必要です。
'use client'
import { useQueryState } from 'nuqs'
export function Demo() {
// 'name' というクエリパラメータを管理(デフォルトは string | null)
const [name, setName] = useQueryState('name')
return (
<>
<input
value={name || ''}
onChange={e => setName(e.target.value || null)}
/>
<p>Hello, {name || 'anonymous visitor'}!</p>
</>
)
}
setName(null) を実行すると、対応するキーがURLから削除されます。
クエリパラメータの削除の際にいちいちdeleteを使ったり、name= のように空白で残したままにするようなことをしなくてもkeyごと消えてくれるのが地味に嬉しいです。
日本語でもcompositionstart とcompositionend のイベントで変換中の補足を行わなくてもよいのが偉い!
パーサーで型安全に利用する
クエリパラメータは本来文字列ですが、nuqsのビルドインパーサーを使用することで、数値や真偽値、日付、JSONなどとして型安全に扱うことができます。
主なビルドインパーサー
- parseAsString: 文字列
- parseAsInteger: 整数
- parseAsBoolean: 真偽値
- parseAsJson: JSONオブジェクト(Zod等でのバリデーションも可能)
デフォルト値の指定
.withDefault() メソッド(またはオプションの defaultValue)を使用することで、パラメータが存在しない場合の初期値を指定でき、返り値の型から null を排除できます。
import { useQueryState, parseAsInteger } from 'nuqs'
const [count, setCount] = useQueryState(
'count',
parseAsInteger.withDefault(0) // countの型は number となる
)
複数のパラメータの更新
関連する複数のクエリパラメータを一度に取得・更新したい場合は、useQueryStates フックが便利です。
import { useQueryStates, parseAsFloat } from 'nuqs'
const [coordinates, setCoordinates] = useQueryStates({
lat: parseAsFloat.withDefault(45.18),
lng: parseAsFloat.withDefault(5.72)
})
const updateLocation = () => {
setCoordinates({ lat: 35.68, lng: 139.69 }) // 同時に更新
}
このフックを使用すると、1回のイベントループ内で複数の状態更新がバッチ処理され、URLの更新が効率的に行われます。
URLのクエリパラメータの名前を指定する
コード内では可読性の高い変数名(例: latitude)を使いつつ、URL上では短縮されたキー名(例: lat)を使いたい場合があります。これは、URLの長さ制限を回避する際にも有効な手法です。
urlKeys オプションを使用することで、このマッピングを指定できます。
const [{ latitude, longitude }, setCoordinates] = useQueryStates(
{
latitude: parseAsFloat.withDefault(0),
longitude: parseAsFloat.withDefault(0),
},
{
urlKeys: {
latitude: 'lat',
longitude: 'lng'
}
}
)
// コード上では latitude を使うが、URLは ?lat=... となる
サーバーコンポーネントでのパーサー利用
サーバーコンポーネントでクエリパラメータをパースする時は、createLoader関数を使用してloader関数が作成できます。
作成したloader関数はloadSearchParams はパース済みのクエリパラメーターを返します。
import { createLoader, parseAsFloat, type SearchParams } from 'nuqs/server'
// Describe your search params, and reuse this in useQueryStates / createSerializer:
export const coordinatesSearchParams = {
latitude: parseAsFloat.withDefault(0),
longitude: parseAsFloat.withDefault(0),
}
export const loadSearchParams = createLoader(coordinatesSearchParams)
type PageProps = {
searchParams: Promise<SearchParams>
}
export default async function Page({ searchParams }: PageProps) {
//ここでsearchParamsをパースできる
const { latitude, longitude } = await loadSearchParams(searchParams)
return <SamplePage lat={latitude} lng={longitude} />
}
function SamplePage({ lat, lng }: { lat: number; lng: number }) {
return (
<div className="flex flex-col gap-4 p-10">
<p className="font-bold text-2xl">
Map: {lat}, {lng}
</p>
</div>
)
}
searchParamsが使えない子サーバーコンポーネントから取得する
searchParams に直接アクセスできない子サーバーコンポーネントからクエリパラメーターを取得したい場合には createSearchParamsCache 関数を使用します。この関数は内部で React の cache() 関数を使用してクエリパラメーターをキャッシュします。
なお、この関数は現在 Next.js でのみ使用可能らしいです。
import { createSearchParamsCache, parseAsInteger, parseAsString, type SearchParams } from 'nuqs/server'
const parser = {
name: parseAsString.withDefault('world'),
count: parseAsInteger.withDefault(0),
}
const searchParamsCache = createSearchParamsCache(parser)
type PageProps = {
searchParams: SearchParams
}
export default async function Page(props: Promise<PageProps>) {
const { searchParams } = await props
// .parse() メソッドは必ず呼び出す必要があることに注意
const { name } = await searchParamsCache.parse(searchParams)
return (
<div className="flex flex-col gap-4 p-10">
<p className="font-bold text-2xl">Hello, {name}!</p>
<ChildComponent />
</div>
)
}
function ChildComponent() {
const count = searchParamsCache.get('count')
return <p className="font-bold text-2xl">Count: {count}</p>
}
まとめ
nuqsを導入することで、以下のメリットが得られます:
-
ボイラープレートの削減:
URLSearchParamsやuseRouterを駆使した複雑なロジックを簡素化できます。 - 型安全性: パーサーにより、文字列からの型変換ミスによるバグを防止できます。
- UXの向上: URLが「信頼できる唯一の情報源(Single Source of Truth)」となるため、リロードやURL共有時にもアプリの状態が正確に再現されます。
複雑な検索フィルタやページネーションを持つプロジェクトにおいて、nuqsは非常に強力なツールとなります。





