Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

react-router — Claude Code skill: TanStack Router hooks

A skill that teaches your coding agent the React bindings for TanStack Router: hook reference, components, and a file-based routing setup on Vite.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add TanStack/router --skill react-router -a claude-code
Open on GitHub

What react-router does

The skill gives the agent a working map of @tanstack/react-router, the React bindings for TanStack Router. It covers the hooks (useRouter, useRouterState, useNavigate, useSearch, useParams, useLoaderData, useMatch, useBlocker, useCanGoBack) and the components (RouterProvider, Link, Outlet, Navigate, CatchBoundary), then walks through file-based routing on Vite step by step.

It is a markdown file with sections and code samples. Install @tanstack/react-router, add @tanstack/router-plugin and @tanstack/react-router-devtools as dev dependencies, register the tanstackRouter plugin in vite.config.ts before react(), create __root.tsx and the route files, and declare the Register interface in main.tsx. Skip that declaration and Link, useNavigate and useSearch lose type safety.

The skill builds on router-core and expects you to read that first. It also repeats two rules: types are inferred, so never cast or annotate them, and loaders run on the client by default. Here is the catch: @tanstack/react-router is a different library from react-router-dom and react-router, with a different API. If either one is already in your dependencies, this skill is not a swap-in replacement, so check what you have installed first.

What it helps with

  • set up file-based routing on Vite from scratch
  • read route params with useParams without hand-written types
  • block navigation away from a form with unsaved changes
  • build breadcrumbs from the active matches
  • move a component onto TanStack Router without mixing it up with react-router-dom

Example prompts for the agent

  • Set up file-based routing with the `tanstackRouter` Vite plugin, a `src/routes/__root.tsx` layout and `RouterProvider` in `src/main.tsx`.
  • Read `page` from the search params on `/products` with `useSearch`, and leave the types inferred.
  • Block navigation away from an unsaved form using `useBlocker` with `shouldBlockFn`.

How to install react-router

1Skills CLI (Vercel)

npx skills add TanStack/router --skill react-router -a claude-code

2Manually: put the files in place

~/.claude/skills/react-router/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/react-router/

From the same set2

The whole set →

Similar

reka-ui onmax A skill for working with Reka UI, a library of unstyled Vue 3 components with full accessibility support. Use it when building interfaces from scratch or working with Nuxt UI and shadcn-vue. Claude Code CX Codex Cursor +1 7,400installs migrate-nativewind-to-uniwind uni-stack A skill that automates migrating React Native projects from NativeWind to Uniwind. It updates configs, removes old packages, rebuilds CSS, and handles all breaking changes. Claude Code CX Codex Cursor +1 2,000installs wpds WordPress Feeds WordPress Design System docs to your agent through an MCP server, so UI work lands on real components and tokens instead of guesses. Claude Code CX Codex Cursor +1 4,000installs narrow-react-prop-types humanlayer A skill that cuts React component prop types back to the contract your live code actually uses, then makes stories and tests match it instead of weakening it. Claude Code CX Codex Cursor +1 1,200installs tanstack-query-best-practices DeckardGer A rule set for TanStack Query in React: query keys, caching, mutations, SSR. Installs into Claude Code, Codex, Cursor or Copilot so data fetching follows one pattern across the repo. Claude Code CX Codex Cursor +1 12,800installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs prototype Matt Pocock Creates throwaway prototypes for checking state logic or exploring UI before committing the decision to the main codebase. Claude Code 986,201installs

Questions about react-router

How do I install react-router in Claude Code?

Run npx skills add TanStack/router --skill react-router -a claude-code. The skill lands at ~/.claude/skills/react-router/SKILL.md.

What does the react-router skill do?

It hands the agent a reference for the TanStack Router React bindings: hooks, components, and the order for setting up file-based routing on Vite.

Is `@tanstack/react-router` the same as `react-router-dom`?

No. They are separate libraries with different APIs, and the skill only covers TanStack Router.

Source text

SKILL.md · MIT
---
name: react-router
description: >-
  React bindings for TanStack Router: RouterProvider, useRouter,
  useRouterState, useMatch, useMatches, useLocation, useSearch,
  useParams, useNavigate, useLoaderData, useLoaderDeps,
  useRouteContext, useBlocker, useCanGoBack, Link, Navigate,
  Outlet, CatchBoundary, ErrorComponent. React-specific patterns
  for hooks, providers, SSR hydration, and createLink with
  forwardRef.
metadata:
  type: framework
  library: tanstack-router
  library_version: '1.166.2'
  framework: react
requires:
  - router-core
sources:
  - TanStack/router:packages/react-router/src
  - TanStack/router:docs/router/guide/creating-a-router.md
  - TanStack/router:docs/router/installation/manual.md
---

# React Router (`@tanstack/react-router`)

This skill builds on router-core. Read `@tanstack/router-core#router-core` first for foundational concepts.

> **CRITICAL**: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values.
> **CRITICAL**: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server.
> **CRITICAL**: Do not confuse `@tanstack/react-router` with `react-router-dom`/`react-router`. They are completely different libraries with different APIs.

## Full Setup: File-Based Routing with Vite

### 1. Install Dependencies

```bash
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools
```

### 2. Configure Vite Plugin

```ts
// 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. Create Root Route

```tsx
// 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. Create Route Files

```tsx
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
  component: HomePage,
})

function HomePage() {
  return <h1>Welcome Home</h1>
}
```

```tsx
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/about')({
  component: AboutPage,
})

function AboutPage() {
  return <h1>About</h1>
}
```

### 5. Create Router Instance and Register Types

```tsx
// 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>,
  )
}
```

## Hooks Reference

All hooks are imported from `@tanstack/react-router`.

### `useRouter()`

Access the router instance directly:

```tsx
import { useRouter } from '@tanstack/react-router'

function InvalidateButton() {
  const router = useRouter()
  return <button onClick={() => router.invalidate()}>Refresh data</button>
}
```

### `useRouterState()`

Subscribe to router state changes. Exposes the entire state and thus incurs
a performance cost. For matches or location favor `useMatches` and `useLocation`.

```tsx
import { useRouterState } from '@tanstack/react-router'

function LoadingIndicator() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  return isLoading ? <div>Loading...</div> : null
}
```

### `useNavigate()`

Programmatic navigation (prefer `<Link>` for user-clickable elements):

```tsx
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 })`

Read validated search params:

```tsx
import { useSearch } from '@tanstack/react-router'

function Pagination() {
  const { page } = useSearch({ from: '/products' })
  return <span>Page {page}</span>
}
```

### `useParams({ from })`

Read path params:

```tsx
import { useParams } from '@tanstack/react-router'

function PostHeader() {
  const { postId } = useParams({ from: '/posts/$postId' })
  return <h2>Post {postId}</h2>
}
```

### `useLoaderData({ from })`

Read data returned from the route loader:

```tsx
import { useLoaderData } from '@tanstack/react-router'

function PostContent() {
  const { post } = useLoaderData({ from: '/posts/$postId' })
  return <article>{post.content}</article>
}
```

### `useMatch({ from })`

Access the full route match (params, search, loader data, context):

```tsx
import { useMatch } from '@tanstack/react-router'

function PostDetails() {
  const match = useMatch({ from: '/posts/$postId' })
  return <div>{match.loaderData.post.title}</div>
}
```

### Other Hooks

All imported from `@tanstack/react-router`:

- **`useMatches()`** — array of all active route matches (useful for breadcrumbs)
- **`useRouteContext({ from })`** — read context from `beforeLoad` or parent routes
- **`useBlocker({ shouldBlockFn })`** — block navigation for unsaved changes
- **`useCanGoBack()`** — returns `boolean`, check if history has entries to go back to
- **`useLocation()`** — current parsed location (`pathname`, `search`, `hash`)
- **`useLinkProps({ to, params?, search? })`** — get `<a>` props for custom link elements
- **`useMatchRoute()`** — returns a function: `matchRoute({ to }) => match | false`

## Components Reference

### `RouterProvider`

Mount the router at the top of your React tree:

```tsx
<RouterProvider router={router} />
```

### `Link`

Type-safe navigation link with `<a>` semantics:

```tsx
<Link to="/posts/$postId" params={{ postId: '42' }}>
  View Post
</Link>
```

### `Outlet`

Renders the matched child route component:

```tsx
function Layout() {
  return (
    <div>
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  )
}
```

### `Navigate`

Declarative redirect component:

```tsx
import { Navigate } from '@tanstack/react-router'

function OldPage() {
  return <Navigate to="/new-page" />
}
```

### `Await`

Renders deferred data from unawaited loader promises with Suspense:

```tsx
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`

Error boundary for component-level error handling (route-level errors use `errorComponent` route option):

```tsx
import { CatchBoundary } from '@tanstack/react-router'
;<CatchBoundary
  getResetKey={() => 'widget'}
  onCatch={(error) => console.error(error)}
  errorComponent={({ error }) => <div>{String(error)}</div>}
>
  <RiskyWidget />
</CatchBoundary>
```

## React-Specific Patterns

### Custom Link Component with `createLink`

Wrap `Link` in a custom component while preserving type safety:

```tsx
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>
  )
}
```

### Reusable Components with Router Hooks

To create a component that uses router hooks across multiple routes, pass a union of route paths as the `from` prop:

```tsx
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" />
```

This pattern avoids `strict: false` (which returns an imprecise union) while keeping the component reusable across specific known routes.

### Auth Provider Must Wrap RouterProvider

If routes use auth context (via `createRootRouteWithContext`), the auth provider must be an ancestor of `RouterProvider`:

```tsx
// 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>
  )
}
```

Or use the `Wrap` router option to provide context without wrapping externally:

```tsx
const router = createRouter({
  routeTree,
  Wrap: ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  ),
})
```

## Common Mistakes

### 1. HIGH: Using React hooks in `beforeLoad` or `loader`

`beforeLoad` and `loader` are NOT React components — they are plain async functions. React hooks cannot be called in them. Pass auth state via router context instead.

```tsx
// 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: Wrapping RouterProvider inside an auth provider incorrectly

Create the router once with an `undefined!` placeholder, then inject live auth via `RouterProvider`'s `context` prop. Do NOT recreate the router on auth changes — this resets caches and rebuilds the tree.

```tsx
// 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: Missing Suspense boundary for `Await`/deferred data

`Await` requires a `<Suspense>` ancestor. Without it, the deferred promise has no fallback UI and throws.

```tsx
// 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>
```

## Cross-References

- `@tanstack/router-core#router-core` — all sub-skills for domain-specific patterns (search params, data loading, navigation, auth, SSR, etc.)

© TanStack · MIT · TanStack/router

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor