jpskill.com
🛠️ 開発・MCP コミュニティ

tanstack-router

Reactでファイル構成に基づいたルーティング設定を、型安全に行い、検索パラメータの検証、データ読み込み、コード分割などを実現することで、より安全で効率的なWebアプリケーション開発を支援するSkill。

📜 元の英語説明(参考)

Type-safe routing for React with file-based routes, validated search params, loaders, and automatic code splitting. Use when someone asks to "set up routing for React", "type-safe router", "TanStack Router", "file-based routing", "search params validation", "replace React Router with something type-safe", or "add route-level data loading". Covers file-based routing, search params with Zod, route loaders, code splitting, and layouts.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Reactでファイル構成に基づいたルーティング設定を、型安全に行い、検索パラメータの検証、データ読み込み、コード分割などを実現することで、より安全で効率的なWebアプリケーション開発を支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o tanstack-router.zip https://jpskill.com/download/15453.zip && unzip -o tanstack-router.zip && rm tanstack-router.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/15453.zip -OutFile "$d\tanstack-router.zip"; Expand-Archive "$d\tanstack-router.zip" -DestinationPath $d -Force; ri "$d\tanstack-router.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して tanstack-router.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → tanstack-router フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

TanStack Router

概要

TanStack Router は、React 向けの完全にタイプセーフなルーターです。すべてのルートパス、検索パラメータ、パスパラメータ、およびローダーは、エンドツーエンドで型付けされています。ルートを変更すると、TypeScript はコンパイル時にすべての壊れたリンクを検出します。自動コード分割、検証済みの検索パラメータ、およびルートレベルのデータローディングを備えたファイルベースのルーティングです。

どのような時に使うか

  • タイプセーフなルーティング(リンク、パラメータ、検索)が必要な React SPA または SSR アプリ
  • React Router から移行し、コンパイル時のルートの安全性を確保したい場合
  • 検証済みで型付けされた検索/クエリパラメータ(単なる string | undefined ではないもの)が必要な場合
  • pending/error 状態でのルートレベルのデータローディング
  • 自動コード分割によるファイルベースのルーティング

手順

セットアップ

npm install @tanstack/react-router
npm install -D @tanstack/router-plugin  # ファイルベースルーティング用の Vite プラグイン
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";

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

ファイルベースルーティング

src/routes/
├── __root.tsx          # ルートレイアウト(すべてのページをラップ)
├── index.tsx           # /
├── about.tsx           # /about
├── users/
│   ├── index.tsx       # /users
│   ├── $userId.tsx     # /users/:userId (動的パラメータ)
│   └── $userId/
│       └── posts.tsx   # /users/:userId/posts (ネスト)
└── settings/
    ├── _layout.tsx     # /settings/* のレイアウトラッパー
    ├── profile.tsx     # /settings/profile
    └── billing.tsx     # /settings/billing

ルートレイアウト

// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from "@tanstack/react-router";

export const Route = createRootRoute({
  component: () => (
    <div>
      <nav className="flex gap-4 p-4 border-b">
        <Link to="/" className="[&.active]:font-bold">Home</Link>
        <Link to="/users" className="[&.active]:font-bold">Users</Link>
        <Link to="/about" className="[&.active]:font-bold">About</Link>
      </nav>
      <main className="p-4">
        <Outlet />
      </main>
    </div>
  ),
});

ローダー付きルート

// src/routes/users/index.tsx — データローディング付きルート
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

// 検索パラメータスキーマ — 検証済みで型付け
const usersSearchSchema = z.object({
  page: z.number().int().positive().catch(1),
  search: z.string().optional(),
  role: z.enum(["all", "admin", "user"]).catch("all"),
});

export const Route = createFileRoute("/users/")({
  // Zod で検索パラメータを検証
  validateSearch: usersSearchSchema,

  // レンダリング前にデータをロード(型付けされた検索パラメータ付き)
  loaderDeps: ({ search }) => ({ search }),
  loader: async ({ deps: { search } }) => {
    const params = new URLSearchParams({
      page: String(search.page),
      ...(search.search && { search: search.search }),
      ...(search.role !== "all" && { role: search.role }),
    });
    const res = await fetch(`/api/users?${params}`);
    return res.json() as Promise<{ users: User[]; total: number }>;
  },

  component: UsersPage,
});

function UsersPage() {
  const { users, total } = Route.useLoaderData();
  const { page, search, role } = Route.useSearch();
  const navigate = Route.useNavigate();

  return (
    <div>
      <h1>Users ({total})</h1>

      <input
        value={search ?? ""}
        onChange={(e) => navigate({ search: { search: e.target.value, page: 1 } })}
        placeholder="Search users..."
      />

      <select
        value={role}
        onChange={(e) => navigate({ search: { role: e.target.value as any, page: 1 } })}
      >
        <option value="all">All roles</option>
        <option value="admin">Admin</option>
        <option value="user">User</option>
      </select>

      {users.map((user) => (
        <Link key={user.id} to="/users/$userId" params={{ userId: user.id }}>
          {user.name}
        </Link>
      ))}

      <button
        disabled={page <= 1}
        onClick={() => navigate({ search: { page: page - 1 } })}
      >
        Previous
      </button>
      <button onClick={() => navigate({ search: { page: page + 1 } })}>
        Next
      </button>
    </div>
  );
}

動的ルートパラメータ

// src/routes/users/$userId.tsx — 型付けされたパラメータを持つ動的ルート
import { createFileRoute, notFound } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$userId")({
  loader: async ({ params: { userId } }) => {
    // userId は string として型付けされています — キャストは不要
    const res = await fetch(`/api/users/${userId}`);
    if (!res.ok) throw notFound();
    return res.json() as Promise<User>;
  },

  notFoundComponent: () => <div>User not found</div>,

  component: UserProfile,
});

function UserProfile() {
  const user = Route.useLoaderData();
  //    ^? User — ローダーの戻り値から完全に型付け

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <Link to="/users/$userId/posts" params={{ userId: user.id }}>
        View Posts
      </Link>
    </div>
  );
}

タイプセーフなリンク

// リンクは完全に型付けされています — 間違ったルートまたは不足しているパラメータ = コンパイルエラー
import { Link } from "@tanstack/react-router";

// ✅ 正しい — ルートが存在し、パラメータが一致
<Link to="/users/$userId" params={{ userId: "123" }}>Profile</Link>

// ✅ 検索パラメータは型付けされています
<Link to="/users" search={{ page: 2, role: "admin" }}>Admin Users</Link>

// ❌ コンパイルエラー — ルートが存在しません
<Link to="/nonexistent">Broken</Link>

// ❌ コンパイルエラー — 必要なパラメータがありません
<Link to="/users/$userId">Missing userId</Link>

例 1: フィルタリングされたデータビューを持つダッシュボード

ユーザープロンプト: 「検索、ページネーション、およびロールフィルタリングをサポートするユーザーリストを持つダッシュボードを構築してください — すべて URL で。」

エージェントは、検証済みの検索パラメータ(page、search、role)、フィルタリングされたデータをフェッチするルートレベルのローダー、およびタイプセーフな TanStack Router をセットアップします。

(原文がここで切り詰められています)

📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

TanStack Router

Overview

TanStack Router is a fully type-safe router for React. Every route path, search param, path param, and loader is typed end-to-end — if you change a route, TypeScript catches every broken link at compile time. File-based routing with automatic code splitting, validated search params, and route-level data loading.

When to Use

  • React SPA or SSR app that needs type-safe routing (links, params, search)
  • Migrating from React Router and want compile-time route safety
  • Need validated and typed search/query params (not just string | undefined)
  • Route-level data loading with pending/error states
  • File-based routing with automatic code splitting

Instructions

Setup

npm install @tanstack/react-router
npm install -D @tanstack/router-plugin  # Vite plugin for file-based routing
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";

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

File-Based Routing

src/routes/
├── __root.tsx          # Root layout (wraps all pages)
├── index.tsx           # /
├── about.tsx           # /about
├── users/
│   ├── index.tsx       # /users
│   ├── $userId.tsx     # /users/:userId (dynamic param)
│   └── $userId/
│       └── posts.tsx   # /users/:userId/posts (nested)
└── settings/
    ├── _layout.tsx     # Layout wrapper for /settings/*
    ├── profile.tsx     # /settings/profile
    └── billing.tsx     # /settings/billing

Root Layout

// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from "@tanstack/react-router";

export const Route = createRootRoute({
  component: () => (
    <div>
      <nav className="flex gap-4 p-4 border-b">
        <Link to="/" className="[&.active]:font-bold">Home</Link>
        <Link to="/users" className="[&.active]:font-bold">Users</Link>
        <Link to="/about" className="[&.active]:font-bold">About</Link>
      </nav>
      <main className="p-4">
        <Outlet />
      </main>
    </div>
  ),
});

Route with Loader

// src/routes/users/index.tsx — Route with data loading
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

// Search params schema — validated and typed
const usersSearchSchema = z.object({
  page: z.number().int().positive().catch(1),
  search: z.string().optional(),
  role: z.enum(["all", "admin", "user"]).catch("all"),
});

export const Route = createFileRoute("/users/")({
  // Validate search params with Zod
  validateSearch: usersSearchSchema,

  // Load data before rendering (with typed search params)
  loaderDeps: ({ search }) => ({ search }),
  loader: async ({ deps: { search } }) => {
    const params = new URLSearchParams({
      page: String(search.page),
      ...(search.search && { search: search.search }),
      ...(search.role !== "all" && { role: search.role }),
    });
    const res = await fetch(`/api/users?${params}`);
    return res.json() as Promise<{ users: User[]; total: number }>;
  },

  component: UsersPage,
});

function UsersPage() {
  const { users, total } = Route.useLoaderData();
  const { page, search, role } = Route.useSearch();
  const navigate = Route.useNavigate();

  return (
    <div>
      <h1>Users ({total})</h1>

      <input
        value={search ?? ""}
        onChange={(e) => navigate({ search: { search: e.target.value, page: 1 } })}
        placeholder="Search users..."
      />

      <select
        value={role}
        onChange={(e) => navigate({ search: { role: e.target.value as any, page: 1 } })}
      >
        <option value="all">All roles</option>
        <option value="admin">Admin</option>
        <option value="user">User</option>
      </select>

      {users.map((user) => (
        <Link key={user.id} to="/users/$userId" params={{ userId: user.id }}>
          {user.name}
        </Link>
      ))}

      <button
        disabled={page <= 1}
        onClick={() => navigate({ search: { page: page - 1 } })}
      >
        Previous
      </button>
      <button onClick={() => navigate({ search: { page: page + 1 } })}>
        Next
      </button>
    </div>
  );
}

Dynamic Route Params

// src/routes/users/$userId.tsx — Dynamic route with typed params
import { createFileRoute, notFound } from "@tanstack/react-router";

export const Route = createFileRoute("/users/$userId")({
  loader: async ({ params: { userId } }) => {
    // userId is typed as string — no casting needed
    const res = await fetch(`/api/users/${userId}`);
    if (!res.ok) throw notFound();
    return res.json() as Promise<User>;
  },

  notFoundComponent: () => <div>User not found</div>,

  component: UserProfile,
});

function UserProfile() {
  const user = Route.useLoaderData();
  //    ^? User — fully typed from loader return

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <Link to="/users/$userId/posts" params={{ userId: user.id }}>
        View Posts
      </Link>
    </div>
  );
}

Type-Safe Links

// Links are fully typed — wrong routes or missing params = compile error
import { Link } from "@tanstack/react-router";

// ✅ Correct — route exists, params match
<Link to="/users/$userId" params={{ userId: "123" }}>Profile</Link>

// ✅ Search params typed
<Link to="/users" search={{ page: 2, role: "admin" }}>Admin Users</Link>

// ❌ Compile error — route doesn't exist
<Link to="/nonexistent">Broken</Link>

// ❌ Compile error — missing required param
<Link to="/users/$userId">Missing userId</Link>

Examples

Example 1: Dashboard with filtered data views

User prompt: "Build a dashboard with users list that supports search, pagination, and role filtering — all in the URL."

The agent will set up TanStack Router with validated search params (page, search, role), route-level loader that fetches filtered data, and type-safe navigation that preserves filter state in the URL.

Example 2: Nested layouts for settings

User prompt: "Create a settings page with sidebar navigation — profile, billing, and team sections."

The agent will create a settings layout route with sidebar Links, nested routes for each section, and loaders for settings data.

Guidelines

  • Search params = state — use URL search params instead of React state for filterable/bookmarkable views
  • Validate search params with Zod.catch() provides defaults for invalid params
  • Loaders run before render — no loading spinners for route-level data
  • notFound() in loaders — throw it to render the notFoundComponent
  • Links are type-checked — changing a route path catches all broken links at compile time
  • File naming = route structure$param for dynamic segments, _layout for layout routes
  • Code splitting is automatic — each route file becomes a separate chunk
  • loaderDeps controls re-fetching — loader re-runs only when deps change
  • TanStack Router + TanStack Query — use together for server state + route state