React Navigationのスケーリング:なぜTanStack Routerがエンタープライズアプリのゲームチェンジャーになるのか

Programming tutorial - IT technology blog
Programming tutorial - IT technology blog

型安全なルーティングへの移行

長年React Routerに頼ってきましたが、エンタープライズ向けのダッシュボードが150以上のユニークなルートを超えたあたりで限界を迎えました。詳細ビューでURLパラメータ名を変更したのに、ネストされたタブ内のuseParams呼び出しを修正し忘れたために、ランタイムクラッシュの対応に追われることが絶えませんでした。TypeScriptがURLを「認識」できなかったため、コンパイラはナビゲーションロジックに関しては実質的に無力だったのです。

6ヶ月前、私たちはコアインフラをTanStack Routerに移行しました。これは単なるライブラリの入れ入れ替えではなく、「型優先(Type-First)」アーキテクチャへの移行でした。移行以来、Sentryのログではナビゲーション関連のエラーが85%減少しました。URLを任意の文字列ではなく構造化されたデータとして扱うことで、かつてQAサイクルを悩ませていた「ページが見つかりません(Page Not Found)」バグを排除できました。

複雑な環境では、単なるコンポーネントの切り替え以上のものが必要です。検索パラメータを検証し、冗長なレンダリングなしでネストされたレイアウトを処理し、すべての内部リンクがビルド時に検証されるシステムが必要です。このガイドでは、実際のプロダクション環境で耐えうる構成について説明します。

インストールと初期設定

まずは依存関係を整理しましょう。最近の高速な開発ループにはほぼ必須となっているViteを使用している場合は、ルートを自動生成するための専用プラグインが必要になります。

npm install @tanstack/react-router
npm install -D @tanstack/router-vite-plugin zod

私はいつもこれにzodを組み合わせています。これはスキーマバリデーションの業界標準であり、煩雑なURL文字列のクリーンアップという重労働を担ってくれます。退屈な作業を自動化するために、vite.config.tsを更新してTanStack Routerプラグインを追加しましょう。

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { TanStackRouterVite } from '@tanstack/router-vite-plugin'

export default defineConfig({
  plugins: [
    react(),
    TanStackRouterVite(),
  ],
})

このプラグインはsrc/routesフォルダを監視し、バックグラウンドでrouteTree.gen.tsファイルを生成します。このファイルはこのエンジンの「頭脳」であり、このライブラリを強力なものにしている完全な型安全性を提供します。

コア構成:レイアウトとファイルベースのルーティング

エンタープライズプロジェクトにおいて、ファイルベースのルーティングは正気を保つための唯一の方法です。これにより、ファイルエクスプローラー上でアプリケーション全体の視覚的なマップが得られます。まずはロジックを収めるためのsrc/routesディレクトリを作成しましょう。

ルート(Root)ルート

すべてのアプリにはグローバルなシェルが必要です。アプリケーションをラップするためにsrc/routes/__root.tsxを作成します。ここは、ナビゲーションバーやグローバルトーストプロバイダー、あるいはメインレイアウトの制約を配置するのに最適な場所です。

import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools'

export const Route = createRootRoute({
  component: () => (
    <>
      <nav className="p-4 flex gap-4 bg-slate-100">
        <Link to="/" className="[&.active]:font-bold">ダッシュボード</Link>
        <Link to="/inventory" className="[&.active]:font-bold">在庫管理</Link>
      </nav>
      <hr />
      <Outlet />
      {process.env.NODE_ENV === 'development' && <TanStackRouterDevtools />}
    </>
  ),
})

<Outlet />は、子ルートがレンダリングされるプレースホルダーとして機能します。Viteプラグインのおかげで、<Link>コンポーネントのtoプロップでは完全なオートコンプリートが利用できるようになります。ルート名を変更すると、コンパイラは即座にコードベース全体の壊れたリンクにフラグを立てます。

Zodによる検索パラメータ(Search Params)の管理

エンタープライズアプリにおける真の「キラー機能」は、検索パラメータの管理です。複雑なフィルタリング、ページネーション、マルチセレクトによるソートなどをURL経由で扱うことがよくあります。従来は、これらをuseSearchParamsから手動でパースしていましたが、これは退屈でエラーが発生しやすい作業でした。

ルーターにバリデーションを任せるために、src/routes/inventory.tsxでスキーマを定義しましょう:

import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const inventorySearchSchema = z.object({
  page: z.number().catch(1),
  filter: z.string().optional(),
  sortBy: z.enum(['name', 'price', 'date']).catch('date'),
})

export const Route = createFileRoute('/inventory')({
  validateSearch: (search) => inventorySearchSchema.parse(search),
  component: InventoryComponent,
})

function InventoryComponent() {
  const { page, sortBy } = Route.useSearch()
  
  return (
    <div className="p-2">
      <h3>在庫管理</h3>
      <p>現在のページ: {page}</p>
      <p>ソート順: {sortBy}</p>
    </div>
  )
}

validateSearchを使用することで、不正なデータに対するファイアウォールを作成できます。ユーザーが手動で?page=not-a-numberと入力した場合、.catch(1)フォールバックが機能し、安全なデフォルト値を提供します。useSearch()は常に完全に型定義されたオブジェクトを返すため、コンポーネントのロジックはクリーンに保たれます。

検証とレジリエンス(回復力)

数百のルートを管理する場合、可視性は不可欠です。組み込みのデバッグツール(Devtools)は非常に優れており、アクティブな一致やローダーの状態をリアルタイムで検査できます。これは、ナビゲーションフロー全体でconsole.logを繰り返すよりもはるかに効率的です。

データローディングの処理

TanStack Routerは、コンポーネントがレンダリングされる前にデータを取得するloaderパターンを使用します。これにより、ページがロードされ、スピナーが表示され、最後にデータが届くという「ローディング・ウォーターフォール」現象が解消されます。

export const Route = createFileRoute('/inventory')({
  validateSearch: (search) => inventorySearchSchema.parse(search),
  loader: ({ search }) => fetchInventoryData(search),
  component: InventoryComponent,
  errorComponent: ({ error }) => <div>在庫データの読み込みエラー: {error.message}</div>,
  pendingComponent: () => <div>読み込み中...</div>,
})

ルートレベルでerrorComponentを定義することで、UIの堅牢性が著しく向上します。在庫APIが失敗しても、ページ内のその特定のセクションだけがエラー状態を表示します。サイドバーやナビゲーションを含むアプリケーションの残りの部分は、完全に機能し、操作可能なまま維持されます。

型安全なナビゲーション

useNavigateフックを使用したプログラムによるナビゲーションも、同レベルの保護を提供します。存在しないルートに遷移しようとしたり、必要な検索パラメータを忘れたりすると、TypeScriptがビルドをブロックします。これにより、ユーザーが経験する古典的な「リンク切れ」を防ぐことができます。

const navigate = useNavigate()

const handleUpdateFilter = (newFilter: string) => {
  navigate({
    to: '/inventory',
    search: (prev) => ({ ...prev, filter: newFilter, page: 1 }),
  })
}

結論

TanStack Routerへの切り替えは、「後戻りできない(one-way door)」決断です。一度URLのタイポでコンパイラエラーを経験すると、文字列ベースのルーティングに戻ることは、リンターなしでコードを書くような感覚になります。初期設定はReact Routerよりも構造化されていますが、不正な形式のURLのデバッグに費やしていた時間を節約できることを考えれば、プロフェッショナルなプロジェクトにおいては明らかにこちらが勝者です。

リファクタリングを始めるなら、まずは検索パラメータのバリデーションから着手してください。これが最も即効性のある投資対効果(ROI)をもたらします。チームが生成されたルートツリーに慣れてくれば、ナビゲーションロジックが本質的に壊れないという確信を持って、より速く機能をリリースできるようになるでしょう。

Share: