TA

tanstack-router-migration

Migrates React Router codebases to TanStack Router with file-based routing.

Install

mkdir -p .claude/skills/tanstack-router-migration && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/4056" && unzip -o skill.zip -d .claude/skills/tanstack-router-migration && rm skill.zip

Installs to .claude/skills/tanstack-router-migration

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Migrate React applications from React Router to TanStack Router with file-based routing. Use when user requests: (1) Router migration, (2) TanStack Router setup, (3) File-based routing implementation, (4) React Router replacement, (5) Type-safe routing, or mentions 'migrate router', 'tanstack router', 'file-based routes'.
323 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Implement file-based routing in React
  • Configure type-safe route navigation
  • Validate search parameters with Zod schemas
  • Register router types for TypeScript inference
  • Migrate React Router hooks to TanStack Router hooks

How it works

It replaces React Router with a file-based routing system that uses a build plugin to generate type-safe route trees and Zod-validated search parameters.

Inputs & outputs

You give it
React Router codebase
You get back
TanStack Router project structure

When to use tanstack-router-migration

  • Switch to file-based routing
  • Implement type-safe router
  • Replace React Router with TanStack

About this skill

React Router to TanStack Router Migration

Migrate React applications from React Router to TanStack Router with file-based routing. This skill provides a structured approach for both incremental and clean migrations.

Critical Rules

ALWAYS:

  • Use file-based routing with routes in src/routes/ directory
  • Use from parameter in all hooks for type safety (useParams({ from: '/path' }))
  • Validate search params with Zod schemas using @tanstack/zod-adapter
  • Configure build tool plugin before creating routes
  • Register router type for full TypeScript inference
  • Use fallback() wrapper for optional search params

NEVER:

  • Edit routeTree.gen.ts (auto-generated file)
  • Use React Router hooks in new code during migration
  • Forget the from parameter (loses type safety)
  • Use string-only validation for search params
  • Skip the build plugin configuration

Dependencies

# Core dependencies
bun add @tanstack/react-router @tanstack/zod-adapter

# Build plugin (choose one based on your bundler)
bun add -d @tanstack/router-plugin

# Optional integrations
bun add nuqs                    # URL state management
bun add @sentry/react           # Error tracking with router integration

Migration Phases

Phase 1: Assessment

Audit existing React Router usage:

# Find all React Router imports
grep -r "from 'react-router" src/ --include="*.tsx" --include="*.ts"
grep -r 'from "react-router' src/ --include="*.tsx" --include="*.ts"

# Find hook usages
grep -r "useParams\|useSearchParams\|useNavigate\|useLocation\|useMatch" src/

Document:

  • React Router version (v5 or v6)
  • Number of routes
  • useParams usage count
  • useSearchParams usage count
  • useNavigate usage count
  • Custom Link components
  • Route guards/protected routes
  • Existing route structure

Phase 2: Setup

1. Configure Build Tool

See references/build-configuration.md for full configs.

Rspack/Rsbuild:

// rsbuild.config.ts
import { TanStackRouterRspack } from '@tanstack/router-plugin/rspack';

export default {
  tools: {
    rspack: (config) => {
      config.plugins?.push(
        TanStackRouterRspack({
          target: 'react',
          autoCodeSplitting: true,
          routesDirectory: './src/routes',
          generatedRouteTree: './src/routeTree.gen.ts',
          quoteStyle: 'single',
          semicolons: true,
        })
      );
      // Prevent rebuild loop
      config.watchOptions = { ignored: ['**/routeTree.gen.ts'] };
      return config;
    },
  },
};

Vite:

// vite.config.ts
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';

export default defineConfig({
  plugins: [
    TanStackRouterVite({
      target: 'react',
      autoCodeSplitting: true,
      routesDirectory: './src/routes',
      generatedRouteTree: './src/routeTree.gen.ts',
    }),
    react(),
  ],
});

2. Configure Linter

// biome.jsonc or eslint config
{
  "files": {
    "ignore": ["**/routeTree.gen.ts"]
  },
  "overrides": [
    {
      "include": ["**/routes/**/*"],
      "linter": {
        "rules": {
          "style": {
            "useFilenamingConvention": "off"  // Allow $param.tsx naming
          }
        }
      }
    }
  ]
}

3. Create Routes Directory

mkdir -p src/routes

Phase 3: Router Creation

Create Router Instance:

// src/app.tsx
import { createRouter, RouterProvider } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { routeTree } from './routeTree.gen';
import { NotFoundPage } from './components/misc/not-found-page';

const queryClient = new QueryClient();

const router = createRouter({
  routeTree,
  context: {
    basePath: getBasePath(),
    queryClient,
  },
  basepath: getBasePath(),
  trailingSlash: 'never',
  defaultNotFoundComponent: NotFoundPage,
});

// Register router type for full TypeScript inference
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router;
  }

  // Extend HistoryState for typed navigation state
  interface HistoryState {
    // Add your custom state properties here
    returnUrl?: string;
    documentId?: string;
    documentName?: string;
  }
}

export function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  );
}

Define Router Context Type:

// src/routes/__root.tsx
import type { QueryClient } from '@tanstack/react-query';

export type RouterContext = {
  basePath: string;
  queryClient: QueryClient;
};

Phase 4: Route Migration

Create Root Layout:

// src/routes/__root.tsx
import { createRootRouteWithContext, Outlet } from '@tanstack/react-router';
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools';
import type { QueryClient } from '@tanstack/react-query';
import { NuqsAdapter } from 'nuqs/adapters/tanstack-router';

export type RouterContext = {
  basePath: string;
  queryClient: QueryClient;
};

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

function RootLayout() {
  return (
    <>
      <NuqsAdapter>
        <ErrorBoundary>
          <AppLayout>
            <Outlet />
          </AppLayout>
        </ErrorBoundary>
      </NuqsAdapter>
      {process.env.NODE_ENV === 'development' && (
        <TanStackRouterDevtools position="bottom-right" />
      )}
    </>
  );
}

File-Based Route Structure:

src/routes/
├── __root.tsx                    # Root layout
├── index.tsx                     # / (root redirect)
├── overview/
│   └── index.tsx                 # /overview
├── topics/
│   ├── index.tsx                 # /topics
│   └── $topicName/
│       ├── index.tsx             # /topics/:topicName
│       └── edit.tsx              # /topics/:topicName/edit
├── security/
│   ├── index.tsx                 # /security (redirect)
│   ├── acls/
│   │   ├── index.tsx             # /security/acls
│   │   ├── create.tsx            # /security/acls/create
│   │   └── $aclName/
│   │       └── details.tsx       # /security/acls/:aclName/details

See references/route-templates.md for complete templates.

Phase 5: Hook Migration

React RouterTanStack Router
useParams()useParams({ from: '/path/$param' })
useSearchParams()routeApi.useSearch() with Zod validation
useNavigate()useNavigate({ from: '/path' })
useLocation()useLocation() (same API)
<Link to="/path"><Link to="/path"> (type-safe)
<Navigate to="/path" /><Navigate to="/path" />

See references/migration-patterns.md for detailed before/after examples.

Navigation State:

Pass typed state between routes using HistoryState:

// Navigating with state
const navigate = useNavigate();
navigate({
  to: '/documents/$documentId',
  params: { documentId },
  state: {
    returnUrl: location.pathname,
    documentName: 'My Document',
  },
});

// Reading state in destination component
import { useLocation } from '@tanstack/react-router';

function DocumentPage() {
  const location = useLocation();
  const { returnUrl, documentName } = location.state;
  // Use state values...
}

useParams Migration:

// Before (React Router)
import { useParams } from 'react-router-dom';
const { id } = useParams<{ id: string }>();

// After (TanStack Router)
import { useParams } from '@tanstack/react-router';
const { id } = useParams({ from: '/items/$id' });

useSearch with Zod Validation:

// In route file
import { fallback, zodValidator } from '@tanstack/zod-adapter';
import { z } from 'zod';

const searchSchema = z.object({
  tab: fallback(z.string().optional(), undefined),
  page: fallback(z.number().optional(), 1),
  q: fallback(z.string().optional(), undefined),
});

export const Route = createFileRoute('/items/')({
  validateSearch: zodValidator(searchSchema),
  component: ItemsPage,
});

// In component
import { getRouteApi, useNavigate } from '@tanstack/react-router';

const routeApi = getRouteApi('/items/');

function ItemsPage() {
  const { tab, page, q } = routeApi.useSearch();
  const navigate = useNavigate({ from: '/items/' });

  const handleTabChange = (newTab: string) => {
    navigate({ search: (prev) => ({ ...prev, tab: newTab }) });
  };
}

Phase 6: Testing

Create Test Utilities:

// src/test-utils.tsx
import { createMemoryHistory, createRouter, RouterProvider } from '@tanstack/react-router';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, type RenderOptions } from '@testing-library/react';
import { routeTree } from './routeTree.gen';
import type { RouterContext } from './routes/__root';

interface RenderWithFileRoutesOptions extends Omit<RenderOptions, 'wrapper'> {
  initialLocation?: string;
  routerContext?: Partial<RouterContext>;
}

export function renderWithFileRoutes(
  ui: React.ReactElement | null = null,
  { initialLocation = '/', routerContext = {}, ...renderOptions }: RenderWithFileRoutesOptions = {}
) {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  const router = createRouter({
    routeTree,
    history: createMemoryHistory({ initialEntries: [initialLocation] }),
    context: { basePath: '', queryClient, ...routerContext },
  });

  function Wrapper({ children }: { children: React.ReactNode }) {
    return (
      <QueryClientProvider client={queryClient}>
        <RouterProvider router={router}>{children}</RouterProvider>
      </QueryClientProvider>
    );
  }

  return {
    ...render(ui ?? <div />, { wrapper: Wrapper, ...renderOptions }),
  

---

*Content truncated.*

When not to use it

  • When editing the auto-generated routeTree.gen.ts file
  • When using React Router hooks in new code

Prerequisites

bun

Limitations

  • Requires build tool plugin configuration
  • Requires migration of all legacy hooks to TanStack equivalents

How it compares

It enforces type safety across the entire routing layer, whereas React Router relies on string-based paths and manual type definitions.

Compared to similar skills

tanstack-router-migration side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
tanstack-router-migration (this skill)16moReviewAdvanced
typescript282moNo flagsBeginner
react-patterns96moNo flagsIntermediate
typescript-skills36moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

More by redpanda-data

View all by redpanda-data

react-best-practices

redpanda-data

Client-side React performance optimization patterns.

2244

code-standards

redpanda-data

TypeScript, React, and JavaScript best practices enforced by Ultracite/Biome.

27

bloblang-authoring

redpanda-data

This skill should be used when users need to create or debug Bloblang transformation scripts. Trigger when users ask about transforming data, mapping fields, parsing JSON/CSV/XML, converting timestamps, filtering arrays, or mention "bloblang", "blobl", "mapping processor", or describe any data transformation need like "convert this to that" or "transform my JSON".

12

component-search

redpanda-data

This skill should be used when users need to discover Redpanda Connect components for their streaming pipelines. Trigger when users ask about finding inputs, outputs, processors, or other components, or when they mention specific technologies like "kafka consumer", "postgres output", "http server", or ask "which component should I use for X".

12

e2e-tester

redpanda-data

Write and run Playwright E2E tests for Redpanda Console using testcontainers. Analyzes test failures, adds missing testids, and improves test stability. Use when user requests E2E tests, Playwright tests, integration tests, test failures, missing testids, or mentions 'test workflow', 'browser testing', 'end-to-end', or 'testcontainers'.

13

form-refactorer

redpanda-data

Refactor legacy forms to use modern Redpanda UI Registry Field components with react-hook-form and Zod validation. Use when user requests: (1) Form refactoring or modernization, (2) Converting Chakra UI or @redpanda-data/ui forms, (3) Updating forms to use Field components, (4) Migrating from legacy form patterns, (5) Implementing forms with react-hook-form and Zod validation.

13

Search skills

Search the agent skills registry