react-router について
`@tanstack/react-router` を使う React 開発者向けのスキルです。ファイルベースルーティングを扱い、型推論とクライアント優先のデータ読み込みを重視します。
主な機能
- 01Vite でのファイルベースルーティング設定
- 02型安全なルーターフックの利用
- 03パラメーター、検索条件、ローダーデータの取得
- 04ナビゲーションとルーター状態の管理
- 05認証コンテキストと遅延データの処理
活用シーン
- 01React アプリでファイルベースルートを設定
- 02型安全なナビゲーションリンクを構築
- 03ルーターローダーデータの取得と表示
- 04認証ルートとナビゲーション制御の実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ tanstack/ router --skill react-routerインストール後、Claude Code で/react-routerを入力して読み込まれたか確認します。
詳細
React Router (@tanstack/react-router)
このスキルは router-core を基盤としています。基本概念については、まず @tanstack/router-core#router-core をお読みください。
CRITICAL:TanStack Router の型は完全に推論されます。型アサーションや推論された値への型注釈は決して追加しないでください。 CRITICAL:TanStack Router はクライアントファーストです。ローダーはデフォルトでサーバーではなく、クライアント上で実行されます。 CRITICAL:
@tanstack/react-routerとreact-router-dom/react-routerを混同しないでください。これらは API が異なる、まったく別のライブラリです。
セットアップ全体:Vite でのファイルベースルーティング
1. 依存関係をインストールする
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools
2. Vite プラグインを設定する
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
// MUST come before react()
tanstackRouter({
target: 'react',
autoCodeSplitting: true,
}),
react(),
],
})
3. ルートルートを作成する
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<nav>
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</nav>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
)
}
4. ルートファイルを作成する
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome Home</h1>
}
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutPage,
})
function AboutPage() {
return <h1>About</h1>
}
5. ルーターインスタンスを作成して型を登録する
// src/main.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('root')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}
フックのリファレンス
すべてのフックは @tanstack/react-router からインポートします。
useRouter()
ルーターインスタンスに直接アクセスします。
import { useRouter } from '@tanstack/react-router'
function InvalidateButton() {
const router = useRouter()
return <button onClick={() => router.invalidate()}>Refresh data</button>
}
useRouterState()
ルーターの状態変化を購読します。状態全体を公開するため、パフォーマンスコストが発生します。マッチまたはロケーションには、useMatches と useLocation を使用してください。
import { useRouterState } from '@tanstack/react-router'
function LoadingIndicator() {
const isLoading = useRouterState({ select: (s) => s.isLoading })
return isLoading ? <div>Loading...</div> : null
}
useNavigate()
プログラムによるナビゲーションを行います(ユーザーがクリックする要素には <Link> を使用してください)。
import { useNavigate } from '@tanstack/react-router'
function AfterSubmit() {
const navigate = useNavigate()
const handleSubmit = async () => {
await saveData()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
return <button onClick={handleSubmit}>Save</button>
}
useSearch({ from })
検証済みの検索パラメーターを読み取ります。
import { useSearch } from '@tanstack/react-router'
function Pagination() {
const { page } = useSearch({ from: '/products' })
return <span>Page {page}</span>
}
useParams({ from })
パスパラメーターを読み取ります。
import { useParams } from '@tanstack/react-router'
function PostHeader() {
const { postId } = useParams({ from: '/posts/$postId' })
return <h2>Post {postId}</h2>
}
useLoaderData({ from })
ルートローダーから返されたデータを読み取ります。
import { useLoaderData } from '@tanstack/react-router'
function PostContent() {
const { post } = useLoaderData({ from: '/posts/$postId' })
return <article>{post.content}</article>
}
useMatch({ from })
ルートマッチ全体(パラメーター、検索、ローダーデータ、コンテキスト)にアクセスします。
import { useMatch } from '@tanstack/react-router'
function PostDetails() {
const match = useMatch({ from: '/posts/$postId' })
return <div>{match.loaderData.post.title}</div>
}
その他のフック
すべて @tanstack/react-router からインポートします。
useMatches()— アクティブなルートマッチの配列(パンくずリストに便利です)useRouteContext({ from })—beforeLoadまたは親ルートのコンテキストを読み取りますuseBlocker({ shouldBlockFn })— 未保存の変更がある場合にナビゲーションをブロックしますuseCanGoBack()—booleanを返します。戻るための履歴エントリーがあるかを確認しますuseLocation()— 現在の解析済みロケーション(pathname、search、hash)useLinkProps({ to, params?, search? })— カスタムリンク要素用の<a>プロパティを取得しますuseMatchRoute()— 関数を返します:matchRoute({ to }) => match | false
コンポーネントのリファレンス
RouterProvider
React ツリーの最上位にルーターをマウントします。
<RouterProvider router={router} />
Link
<a> のセマンティクスを備えた、型安全なナビゲーションリンクです。
<Link to="/posts/$postId" params={{ postId: '42' }}>
View Post
</Link>
Outlet
マッチした子ルートのコンポーネントをレンダリングします。
function Layout() {
return (
<div>
<Sidebar />
<main>
<Outlet />
</main>
</div>
)
}
Navigate
宣言的なリダイレクトコンポーネントです。
import { Navigate } from '@tanstack/react-router'
function OldPage() {
return <Navigate to="/new-page" />
}
Await
未 await のローダープロミスから返された遅延データを、Suspense を使ってレンダリングします。
import { Await } from '@tanstack/react-router'
import { Suspense } from 'react'
function PostWithComments() {
const { deferredComments } = Route.useLoaderData()
return (
<div>
<h1>Post</h1>
<Suspense fallback={<div>Loading comments...</div>}>
<Await promise={deferredComments}>
{(comments) => (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
)}
</Await>
</Suspense>
</div>
)
}
CatchBoundary
コンポーネントレベルのエラー処理を行うエラーバウンダリーです(ルートレベルのエラーには、ルートオプションの errorComponent を使用します)。
import { CatchBoundary } from '@tanstack/react-router'
;<CatchBoundary
getResetKey={() => 'widget'}
onCatch={(error) => console.error(error)}
errorComponent={({ error }) => <div>{String(error)}</div>}
>
<RiskyWidget />
</CatchBoundary>
React 固有のパターン
createLink によるカスタムリンクコンポーネント
型安全性を維持しながら、Link をカスタムコンポーネントでラップします。
import { createLink } from '@tanstack/react-router'
import { forwardRef, type ComponentPropsWithoutRef } from 'react'
const StyledLinkComponent = forwardRef<
HTMLAnchorElement,
ComponentPropsWithoutRef<'a'>
>((props, ref) => (
<a ref={ref} {...props} className={`styled-link ${props.className ?? ''}`} />
))
const StyledLink = createLink(StyledLinkComponent)
// Usage — same type-safe props as Link
function Nav() {
return (
<StyledLink to="/posts/$postId" params={{ postId: '42' }}>
Post
</StyledLink>
)
}
ルーターフックを使う再利用可能なコンポーネント
複数のルートでルーターフックを使うコンポーネントを作成するには、from プロパティにルートパスのユニオンを渡します。
function PostIdDisplay({ from }: { from: '/posts/$id' | '/drafts/$id' }) {
const { id } = useParams({ from })
return <span>ID: {id}</span>
}
// Usage in different route components
<PostIdDisplay from="/posts/$id" />
<PostIdDisplay from="/drafts/$id" />
このパターンでは、不正確なユニオンを返す strict: false を避けつつ、既知の特定のルート間でコンポーネントを再利用できます。
Auth Provider は RouterProvider をラップする必要がある
ルートで認証コンテキスト(createRootRouteWithContext 経由)を使う場合、認証プロバイダーは RouterProvider の祖先でなければなりません。
// CORRECT — AuthProvider wraps RouterProvider
function App() {
return (
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
)
}
// WRONG — RouterProvider outside auth provider
function App() {
return (
<RouterProvider router={router}>
<AuthProvider>{/* ... */}</AuthProvider>
</RouterProvider>
)
}
または、Wrap ルーターオプションを使って、外部からラップせずにコンテキストを提供します。
const router = createRouter({
routeTree,
Wrap: ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
})
よくある間違い
1. 重大:beforeLoad または loader で React フックを使う
beforeLoad と loader は React コンポーネントではなく、通常の非同期関数です。その中で React フックを呼び出すことはできません。代わりに、ルーターコンテキスト経由で認証状態を渡してください。
// WRONG — useAuth is a React hook, cannot be called here
beforeLoad: () => {
const auth = useAuth()
if (!auth.user) throw redirect({ to: '/login' })
}
// CORRECT — read auth from router context
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
}
2. HIGH:RouterProvider を認証プロバイダー内に誤ってラップする
ルーターは undefined! プレースホルダーを使用して一度だけ作成し、RouterProvider の context プロパティを通じて最新の認証情報を渡します。認証情報の変更時にルーターを再作成しないでください。キャッシュがリセットされ、ツリーが再構築されます。
// CORRECT — create router once, inject live auth via context prop
const router = createRouter({
routeTree,
context: { auth: undefined! }, // placeholder, filled by RouterProvider
})
function InnerApp() {
const auth = useAuth()
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<AuthProvider>
<InnerApp />
</AuthProvider>
)
}
3. MEDIUM:Await/遅延データの Suspense 境界がない
Await には <Suspense> の祖先要素が必要です。ない場合、遅延プロミスのフォールバック UI が表示されず、エラーがスローされます。
// WRONG — no Suspense boundary
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
// CORRECT — wrap in Suspense
<Suspense fallback={<div>Loading...</div>}>
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
</Suspense>
相互参照
@tanstack/router-core#router-core— ドメイン固有のパターンに関するすべてのサブスキル(検索パラメーター、データ読み込み、ナビゲーション、認証、SSR など)