🚀

nuqs 2.5.0 アップデート内容のまとめ

に公開

はじめに 🚀

2024年8月22日、タイプセーフなURL状態管理ライブラリである nuqs のバージョン 2.5.0 がリリースされました。

https://nuqs.47ng.com/blog/nuqs-2.5

このバージョンでは7つのベータリリースを経て、多くの便利な機能が追加されています。
特に注目すべき新機能を紹介すると次の通りです。

  • 高頻度の入力に対するURL更新を制御する Debounce 機能
  • コンポーネントの再レンダリングを最適化する Key Isolation
  • 型推論と実行時バリデーションを提供する Standard Schema
  • TanStack Router への実験的サポート

この記事では、これらの新機能について紹介します。

Debounce 機能 ⏱️

nuqs 2.5.0 で最も注目すべき機能の一つが、limitUrlUpdates オプションによる Debounce 機能 です。この機能により、従来の throttleMs オプションからより精密なURL更新制御が可能になりました。

検索フォームやフィルター機能において、ユーザーの入力ごとにURL更新が発生すると、高頻度なURL更新による処理負荷や、秒間数十回のAPI呼び出し発生、history: 'push' 使用時の履歴エントリ肥大化などの問題が生じることがあります。

nuqs では debounce()throttle() の両方がサポートされており、用途に応じて使い分けることができます。Debounce は最後の入力を重視するため検索フォームに適しており、Throttle は継続的なフィードバックを提供するためスライダーやリアルタイム更新に適しています。

詳細な実装方法や具体的なコード例については、以下の記事で説明しています。

https://zenn.dev/chot/articles/ae1d907c72d6e3

Key Isolation 🔗

概要

Key Isolation は nuqs 2.5.0 で導入された パフォーマンス最適化機能 です。この機能により、コンポーネントは自分が管理するURL パラメータが変更された場合のみ再レンダリングされるようになります。

従来の実装では、どのURL パラメータが変更されても、nuqs を使用するすべてのコンポーネントが再レンダリングされる可能性がありました。Key Isolation はこの問題を解決し、大規模なアプリケーションでのパフォーマンスを改善します。

対応フレームワーク

Key Isolation は以下のフレームワークでサポートされています。

  • React SPA
  • React Router
  • Remix
  • TanStack Router

基本的な使い方

Key Isolation は自動的に動作するため、特別な設定は必要ありません。

function ProductFilters() {
  const [category, setCategory] = useQueryState('category', parseAsString)
  const [sort, setSort] = useQueryState('sort', parseAsString)

  // categoryまたはsortが変更された時のみ再レンダリング
  console.log('ProductFilters rendered')

  return (
    <div>
      <select value={category || ''} onChange={(e) => setCategory(e.target.value)}>
        <option value="">全て</option>
        <option value="electronics">電子機器</option>
      </select>
      <select value={sort || ''} onChange={(e) => setSort(e.target.value)}>
        <option value="">並び順</option>
        <option value="price">価格順</option>
      </select>
    </div>
  )
}

パフォーマンス効果

Key Isolation により、無駄な再レンダリングを防げます。

従来の動作

/?category=electronics に変更
↓
nuqs を使う全てのコンポーネントが再レンダリング
- ProductFilters ✅(必要)
- 他のコンポーネント ❌(不要)

Key Isolation 有効時

/?category=electronics に変更
↓
category パラメータを使うコンポーネントのみ再レンダリング
- ProductFilters ✅(必要)
- 他のコンポーネント ⏸️(変更なし)

Standard Schema 📋

概要

Standard Schema は nuqs 2.5.0 で導入された バリデーション機能 です。この機能により、検索パラメータの定義から Standard Schema 互換のバリデーター を作成できるようになりました。

Standard Schema は、TypeScript のバリデーションライブラリ間で共通のインターフェースを提供する仕様です。nuqs がこの仕様に対応することで、tRPC や TanStack Router などの他のツールとの統合が簡素化されました。

基本的な使い方

import {
  createStandardSchemaV1,
  parseAsInteger,
  parseAsString,
} from 'nuqs' // または 'nuqs/server'

// 1. 検索パラメータを通常通り定義
export const searchParams = {
  searchTerm: parseAsString.withDefault(''),
  maxResults: parseAsInteger.withDefault(10),
  category: parseAsString
}

// 2. Standard Schema 互換のバリデーターを作成
// → tRPC、TanStack Router などで型安全に再利用可能
export const validateSearchParams = createStandardSchemaV1(searchParams)

tRPC との統合

Standard Schema により、tRPC のプロシージャで nuqs の検索パラメータ定義を直接使用できるようになりました。

import { router, publicProcedure } from './trpc'
import { validateSearchParams } from './search-params'

export const appRouter = router({
  // 3. tRPCのプロシージャで直接使用
  // → フロントエンドのURL状態と同じ型定義でAPIのバリデーションが実行される
  search: publicProcedure
    .input(validateSearchParams)
    .query(async ({ input }) => {
      // inputは型安全で、searchTerm、maxResults、categoryプロパティを持つ
      const { searchTerm, maxResults, category } = input

      // データベースクエリなどの処理
      return await searchProducts({
        query: searchTerm,
        limit: maxResults,
        category
      })
    }),

  getProductCategories: publicProcedure
    .query(async () => {
      return await getCategories()
    })
})

export type AppRouter = typeof appRouter

Standard Schema の効果

Standard Schema により、nuqs のパーサー定義から他のツールで利用可能なバリデーターを生成できるようになりました。

主要なメリット

  1. ツール間の相互運用性

    • tRPC、TanStack Form、TanStack Router など様々なライブラリで同じバリデーションロジックを再利用
    • 「一度統合すれば、どこでもバリデーション」を実現
  2. 型安全性の一貫性

    • フロントエンドのURL状態からバックエンドのAPIまで、型定義が自動的に連携
    • TypeScript の型推論が各ツール間で共有される
  3. 開発体験の向上

    • 各ライブラリ専用のアダプターを作成する必要がなくなる
    • URL パラメータの定義を一箇所で管理し、複数の場所で活用可能

Standard Schema は標準仕様として策定されており、Zod、Valibot、ArkType などの主要なバリデーションライブラリが共通インターフェースを実装することで、エコシステム全体の統一を図っています。

TanStack Router サポート 🛣️

nuqs 2.5.0 では TanStack Router への実験的サポートが追加されました。この機能は現在ベータ段階であり、補完的な機能として位置づけられています。

注意点

このサポートは nuqs を使用する外部ライブラリとの互換性 を目的としており、TanStack Router アプリ自体では通常必要ありません。TanStack Router は既に優れた型安全なURL状態管理APIを提供しているため、通常はそちらを使用するのが良いです。

参考: https://x.com/nuqs47ng/status/1959166202036490354

セットアップ

nuqs を使用するには、他のフレームワークと同様に専用のアダプターを設定します。

// app/root.tsx (TanStack Router)
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router'
import { Outlet } from '@tanstack/react-router'

export default function App() {
  return (
    <NuqsAdapter>
      <Outlet />
    </NuqsAdapter>
  )
}

基本的な使い方

TanStack Router では、createStandardSchemaV1 を使用してルートの検索パラメータバリデーションと統合できます。

import { createFileRoute } from '@tanstack/react-router'
import { useQueryState, parseAsString, parseAsInteger, createStandardSchemaV1 } from 'nuqs'

const searchParams = {
  q: parseAsString.withDefault(''),
  page: parseAsInteger.withDefault(1)
}

export const Route = createFileRoute('/search')({
  validateSearch: createStandardSchemaV1(searchParams, { partialOutput: true }),
  component: SearchPage
})

function SearchPage() {
  const [query, setQuery] = useQueryState('q', parseAsString.withDefault(''))
  const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <p>ページ: {page}</p>
    </div>
  )
}

TanStack Router サポートは、nuqs の主要機能に加えてさらなる選択肢を提供する補完的な機能として開発が続けられています。

その他の改善点 ✨

nuqs 2.5.0 では、上記の主要機能以外にも多くの改善が行われました。

Const modifier for literal parsers

parseAsStringLiteral で const 修飾子がサポートされ、より正確な型推論が可能になりました。

// 従来
const statusParser = parseAsStringLiteral(['draft', 'published', 'archived'])
// 型: string

// 2.5.0 以降
const statusParser = parseAsStringLiteral(['draft', 'published', 'archived'] as const)
// 型: 'draft' | 'published' | 'archived'

const [status, setStatus] = useQueryState('status', statusParser)
// status の型が正確に推論される

これにより、より正確な型推論により開発体験が向上しました。

Enhanced default options for NuqsAdapter

NuqsAdapter でグローバルなデフォルトオプションを設定できるようになりました。

import { NuqsAdapter } from 'nuqs/adapters/next/app'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <NuqsAdapter
          defaultOptions={{
            history: 'push',
            shallow: false,
            scroll: true,
            clearOnDefault: true
          }}
        >
          {children}
        </NuqsAdapter>
      </body>
    </html>
  )
}

これにより、アプリケーション全体で一貫したオプションを適用できます。

パフォーマンス最適化

バンドルサイズの最適化

nuqs 2.5.0 では ゼロランタイム依存 を維持しながら、バンドルサイズを 5.5KB 未満 に抑えています。

nuqs@2.4.x: ~6.2KB (gzipped)
nuqs@2.5.0: ~5.4KB (gzipped) ⬇️ 13% 削減

Tree Shaking の改善

tree shaking の改善により、使用していない機能がバンドルから除外されるようになりました。

// 使用していない parsers は自動的にバンドルから除外される
import { parseAsString, parseAsInteger } from 'nuqs'
// parseAsFloat, parseAsBoolean などは含まれない

API の改善

defaultRateLimit の導入

カスタムレート制限を無効化し、デフォルトの動作に戻すための defaultRateLimit が追加されました。

import { debounce, defaultRateLimit } from 'nuqs'

const [search, setSearch] = useQueryState('q', {
  limitUrlUpdates: debounce(1000) // 通常は1秒のdebounce
})

// 特定の操作では即座に更新
const handleSubmit = () => {
  setSearch(value, {
    limitUrlUpdates: defaultRateLimit // デフォルトに戻す
  })
}

urlKeys の型改善

urlKeys オプションの型推論が改善され、より安全なコードが書けるようになりました。

import { type UrlKeys } from 'nuqs'

const parsers = {
  latitude: parseAsFloat.withDefault(35.6762),
  longitude: parseAsFloat.withDefault(139.6503)
}

// 型安全な urlKeys の定義
const urlKeys: UrlKeys<typeof parsers> = {
  latitude: 'lat', // ✅ OK
  longitude: 'lng', // ✅ OK
  // invalidKey: 'invalid' // ❌ コンパイルエラー
}

Next.js 15.5 Typed Routes 対応

nuqs 2.5で追加された機能というわけではありませんが、Next.js 15.5 で導入されたtyped routes 機能に向けて、公式XでcreateTypedLink() ユーティリティ関数がポストされました。この関数により、型安全なリンク生成が可能になります。

https://x.com/nuqs47ng/status/1958643771643764893

これらの改善により、nuqs 2.5.0 は より安定高速型安全 なライブラリになりました。

まとめ 📌

nuqs 2.5.0 は、URL状態管理ライブラリとしての機能性を大きく向上させるアップデートです。このリリースにより、開発者はより型安全でパフォーマンスに優れたWebアプリケーションを構築できるようになりました。

以上です!

Discussion