mcpField
T
tanstack-router

tanstack-router

github.com/mindrally開発30077 回インストール4 週間前に更新
リポジトリ

tanstack-router について

TanStack Router向けの開発スキルです。React開発者を対象に、ファイルベースのルート設定、検索パラメーターの検証、loader、アクセスガード、型付きナビゲーションを解説します。

主な機能

  1. 01型安全なルートとナビゲーション
  2. 02ファイルベースのルート規約
  3. 03検索パラメーターの検証
  4. 04loaderによるデータ取得
  5. 05beforeLoadによるアクセス確認

活用シーン

  1. 01Reactアプリへの型付きルート追加
  2. 02クエリパラメーターの検証
  3. 03データ読み込み前のルート保護

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mindrally/skills --skill tanstack-router

インストール後、Claude Code で/tanstack-routerを入力して読み込まれたか確認します。

詳細

TanStack Router

TanStack Router は、ルート、ローダー、パラメーター、検索パラメーターを型のない文字列ではなく、第一級の型付きデータとして扱う、React 用の完全に型安全なルーターです。

新しいルートを追加するワークフロー

  1. ルートファイルを作成する — ファイルベースルーティングの規約(下記参照)に従って、src/routes/ の下にファイルを追加します。
  2. ルートを定義する — createFileRoute('/path')({...}) から Route をエクスポートし、component を指定します。必要に応じて、loader、validateSearch、beforeLoad、errorComponent、pendingComponent も指定します。
  3. 検索パラメーターを検証する — ルートでクエリパラメーターを読み取る場合は、Zod スキーマを定義して validateSearch として渡します。
  4. データを読み込む — データはコンポーネントの useEffect ではなく、loader で取得します。キャッシュが必要な場合は、TanStack Query と queryClient.ensureQueryData を連携させます。
  5. アクセスを制御する — 事前条件を満たさない場合に throw redirect({ to: '/login' }) する beforeLoad チェック(認証など)を追加します。
  6. ルートへのリンクを作成する — <Link to="/path" params={...} search={...}> で遷移し、すべての呼び出し箇所でコンパイラーにパラメーターと検索パラメーターを検証させます。
  7. ルートツリーを再生成する — アプリの実行またはビルドの前に、routeTree.gen.ts が再生成されていることを確認します(Vite プラグインの開発サーバー/ビルドでは自動的に再生成されます)。

基本原則

  • TanStack Router は 100% 型安全です。手動で型変換する代わりに、パラメーター、検索パラメーター、ローダーデータには TypeScript のジェネリクスを活用します。
  • 手動でルートツリーを構築するよりも、拡張性の高いファイルベースルーティングを @tanstack/router-vite-plugin(または @tanstack/router-plugin/vite)とともに使用します。
  • ルートは必ず、createFileRoute(リーフ/ネストルート)または createRootRoute/createRootRouteWithContext(ルート)で定義します。
  • ルートデータの読み込みは、コンポーネントの useEffect ではなく、loader 関数で行います。これにより、事前読み込み、並列読み込み、保留中/エラー状態が利用できます。
  • 検索パラメーターは第一級の状態です。読み取るたびに型付けと検証が行われるよう、必ず Zod(または別の標準スキーマバリデーター)でスキーマを定義します。

ファイルベースのルート規約

src/routes/
  __root.tsx          ← Root layout
  index.tsx           ← / route
  posts/
    index.tsx         ← /posts
    $postId.tsx       ← /posts/:postId (dynamic segment)
    _layout.tsx       ← Layout route (no path segment)
  _auth/               ← Pathless auth layout group
    dashboard.tsx
  • セグメントの先頭にアンダースコアを付けると(_layout、_auth)、グループ化や共通 UI 専用のパスレスレイアウトルートが作成されます。
  • $ プレフィックス($postId)は、Route.useParams() に対応する動的パスセグメントを示します。
  • フォルダー内の index.tsx は、追加のセグメントなしで、そのフォルダー自体のパスに一致します。

ルート定義

export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => fetchPost(params.postId),
  component: PostComponent,
  errorComponent: ({ error }) => <ErrorBanner message={error.message} />,
  pendingComponent: () => <PostSkeleton />,
})

function PostComponent() {
  const post = Route.useLoaderData()     // type-safe
  const { postId } = Route.useParams()   // type-safe
  return <div>{post.title}</div>
}
  • errorComponent はローダーがエラーをスローしたときにレンダリングされます。pendingComponent はローダーの処理中にレンダリングされます(defaultPendingMs の設定によります)。
  • ローダーの戻り値とスローされたエラーは、どちらも完全に型付けされ、それぞれ useLoaderData() と errorComponent に渡されます。

型安全な検索パラメーター

  • 検索パラメーターは必ず Zod と validateSearch で定義します。
  • Route.useSearch() でアクセスします。型安全性と検証を回避してしまうため、window.location.search や URLSearchParams を直接読み取ってはいけません。
const searchSchema = z.object({
  page: z.number().int().min(1).default(1),
  q: z.string().optional(),
})

export const Route = createFileRoute('/search')({
  validateSearch: searchSchema,
  component: SearchPage,
})

function SearchPage() {
  const { page, q } = Route.useSearch()
  const navigate = Route.useNavigate()

  return (
    <button onClick={() => navigate({ search: (prev) => ({ ...prev, page: page + 1 }) })}>
      Next page
    </button>
  )
}

ナビゲーション

  • 内部ナビゲーションには <Link> を使用します。ページ全体が再読み込みされ、クライアント側ルーティングの状態が失われるため、生の <a href> は使用しないでください。
  • 型付けされた params と search を必ず渡します。TypeScript コンパイラーが、ビルド時にルートパラメーターの不足や型の不一致を検出します。
<Link to="/posts/$postId" params={{ postId: '123' }}>View Post</Link>
<Link to="/search" search={{ page: 1, q: 'react' }}>Search</Link>

ローダーと TanStack Query の連携

  • ローダーが TanStack Query を通じてデータを事前取得・キャッシュできるように、QueryClient をルーターコンテキストに配置します。取得ロジックの重複を避けられます。
  • ローダーでは fetchQuery ではなく ensureQueryData を使用します。すでにキャッシュされているクエリを再取得せずに再利用できます。
export const Route = createFileRoute('/posts')({
  loader: ({ context: { queryClient } }) =>
    queryClient.ensureQueryData(postsQueryOptions()),
  component: PostsPage,
})

function PostsPage() {
  const { data: posts } = useSuspenseQuery(postsQueryOptions())
  return <PostList posts={posts} />
}

依存性注入のためのルーターコンテキスト

// __root.tsx
interface RouterContext {
  queryClient: QueryClient
  auth: AuthState
}

export const Route = createRootRouteWithContext<RouterContext>()({
  component: RootLayout,
})

// main.tsx
const router = createRouter({ routeTree, context: { queryClient, auth } })
  • ルーターコンテキストは context 引数を介して、すべてのルートの loader と beforeLoad に渡されます。これにより、プロパティーのバケツリレーやグローバルシングルトンを使わずに、各ルートから共有依存関係へ型付きでアクセスできます。

認証ガード

export const Route = createFileRoute('/_auth/dashboard')({
  beforeLoad: ({ context }) => {
    if (!context.auth.isAuthenticated) throw redirect({ to: '/login' })
  },
  component: Dashboard,
})
  • beforeLoad はローダーの実行前、コンポーネントのレンダリング前に実行されるため、認証チェック、機能フラグによる制御、リダイレクトに適しています。
  • コンポーネント内で命令的に画面遷移するより、throw redirect(...) を優先してください。SSR、プリロード、クライアント側の画面遷移のいずれでも動作します。

パフォーマンス

  • ルーターに defaultPreload: 'intent' を設定すると、リンクにホバーまたはフォーカスしたときにルートのデータがプリフェッチされ、瞬時に画面遷移するように感じられます。
  • 大規模なアプリでは、ルートコンポーネントのコード分割に React.lazy(またはルーター組み込みのコード分割機能である .lazy() ルートファイル)を使用してください。
  • @tanstack/router-devtools をインストールし、開発時に <TanStackRouterDevtools /> をレンダリングすると、ルートの一致、保留中の状態、キャッシュを確認できます。

よくある間違い

  • ルートコンポーネント内で loader ではなく useEffect を使ってデータを取得すると、プリロードができなくなり、レンダリング後に取得するウォーターフォールが発生します。
  • Route.useSearch() ではなく生の window.location.search を読み取ると、検索パラメーターのスキーマと型安全性を迂回してしまいます。
  • beforeLoad ではなくコンポーネント内だけで認証チェック(useEffect によるリダイレクト)を行うと、リダイレクト前に保護対象のコンテンツが一瞬表示されます。
  • 新しいルートパラメーターや検索フィールドを <Link> の呼び出し箇所に追加し忘れないでください。手動でテストするのではなく、コンパイラーに検出させましょう。

コメント