TanStack Router mit Claude Code: Type-Safe Routing 2026

TanStack Router ist die erste vollständig typsichere Router-Lösung für React — jede Route, jeder Parameter, jeder Search Param ist TypeScript-inferred. Claude Code kennt alle Patterns: File-Based Routing, Loaders, beforeLoad Guards, Nested Layouts und Code Splitting ohne Konfigurationsaufwand.

TanStack Router vs React Router: Der entscheidende Vergleich

Lange war React Router die unangefochtene Wahl für React-Routing. TanStack Router hat das 2024 grundlegend verändert — und Claude Code nutzt es standardmäßig für alle neuen React-Projekte, die Type-Safety ernst nehmen.

VergleichWarum TanStack Router React Router ablöst

FeatureTanStack RouterReact Router v6
Route-Parameter typsicher✓ 100% inferiert✗ manuell casten
Search Params typsicher✓ Zod-Schema✗ string only
Loader-Typen propagieren✓ automatisch~ teilweise
File-Based Routing✓ Vite Plugin✗ manuell
Devtools eingebaut✓ Browser Panel✗ nicht vorhanden
Code Splitting automatisch✓ per Route~ lazy() nötig
Context pro Route✓ routerContext✗ manuell
Pending / Error UI✓ eingebaut~ Suspense nötig
Claude Code Empfehlung: Für jedes neue React-Projekt ohne Server-Rendering → TanStack Router. Für Next.js/Remix bleiben deren integrierten Router die bessere Wahl. Claude erkennt den Projektkontext und wählt automatisch die passende Lösung.

Type SafetyWas "vollständig typisiert" wirklich bedeutet

// Prompt: "Zeig mir warum TanStack Router typsicher ist vs React Router" // ❌ React Router — keine Typen, Runtime-Fehler möglich const { userId, orgId } = useParams() // userId ist string | undefined — TypeScript hilft nicht // Tippfehler "userID" → undefined, kein Compile-Fehler // ✅ TanStack Router — vollständig inferiert const { userId, orgId } = Route.useParams() // userId ist EXAKT string — niemals undefined // Tippfehler "userID" → TypeScript-Fehler sofort // navigate({ to: '/users/$userId', params: { userId: 42 } }) → Fehler (number statt string) // Search Params — vollständig validiert mit Zod const { page, filter, sortBy } = Route.useSearch() // page ist number (nicht string!), filter ist 'active'|'archived', sortBy hat Default // URL: /users?page=2&filter=active → TypeScript weiss genau was rauskommt

File-Based Routing Setup mit dem Vite Plugin

Der schnellste Weg zu einem vollständig konfigurierten TanStack-Router-Projekt — Claude Code erledigt Installation, Konfiguration und erste Routen in einem Prompt.

SetupProjekt erstellen und konfigurieren

# Prompt: "Neues Vite+React+TypeScript Projekt mit TanStack Router File-Based Routing" npm create vite@latest my-app -- --template react-ts cd my-app npm install @tanstack/react-router @tanstack/router-devtools npm install -D @tanstack/router-plugin
// vite.config.ts — Vite Plugin einbinden import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { TanStackRouterVite } from '@tanstack/router-plugin/vite' export default defineConfig({ plugins: [ TanStackRouterVite(), // ← Routes automatisch generieren react(), ], })
// src/routes/__root.tsx — Root Layout Route import { createRootRoute, Link, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/router-devtools' export const Route = createRootRoute({ component: () => ( <> <nav> <Link to="/">Home</Link>{' | '} <Link to="/users">Users</Link> </nav> <Outlet /> {/* ← Kind-Routen rendern hier */} <TanStackRouterDevtools /> </> ), })
// src/routes/index.tsx — Home Route (/) import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/')({ component: () => <div>Home Page</div>, }) // src/routes/users/index.tsx — Users Liste (/users) export const Route = createFileRoute('/users/')({ component: () => <div>Users List</div>, }) // src/routes/users/$userId.tsx — User Detail (/users/42) export const Route = createFileRoute('/users/$userId')({ component: () => { const { userId } = Route.useParams() // ← string, typsicher return <div>User {userId}</div> }, })
Auto-Generierung: Das Vite Plugin beobachtet das routes/-Verzeichnis und generiert automatisch routeTree.gen.ts — alle Route-Typen werden daraus abgeleitet. Niemals diese Datei manuell editieren.

SetupDateistruktur und Naming Conventions

src/routes/ __root.tsx → Root Layout (/) index.tsx → Home Page (/) about.tsx → /about users/ index.tsx → /users $userId.tsx → /users/:userId (dynamisch) $userId/ edit.tsx → /users/:userId/edit posts.tsx → /users/:userId/posts _auth/ → Underscore = Pathless Layout (kein URL-Segment) dashboard.tsx → /dashboard (mit Auth-Guard aus _auth) settings.tsx → /settings (mit Auth-Guard aus _auth) (admin)/ → Klammern = Grouped Routes (kein URL-Segment) users.tsx → /users (admin-spezifisch) files/ $.tsx → /files/* (Catch-All)
Wichtig — Dateinamen sind URL-Segmente: $userId.tsx → Parameter userId. _layout.tsx → Pathless Layout (kein eigenes URL-Segment). (group) → Route Grouping ohne URL-Auswirkung. Claude Code kennt alle Konventionen und benennt Dateien korrekt.

Type-Safe Navigation mit useNavigate und Link

Navigation in TanStack Router ist vollständig typisiert — falsche Routen, fehlende Parameter oder falsche Typen werden bereits beim Schreiben als TypeScript-Fehler markiert, nicht erst zur Laufzeit.

Type SafeuseNavigate — Programmatische Navigation

// Prompt: "Typsichere Navigation nach User-Detail mit optionalen Query-Params" import { useNavigate } from '@tanstack/react-router' function UserActions({ userId }: { userId: string }) { const navigate = useNavigate() // ✅ Korrekt — TypeScript prüft alles const goToUser = () => navigate({ to: '/users/$userId', params: { userId }, // ← Pflichtfeld, muss string sein search: { tab: 'profile' }, // ← optional, validiert gegen Schema }) // ✅ Relative Navigation const goToEdit = () => navigate({ from: '/users/$userId', to: './edit', // ← relativ zur aktuellen Route params: { userId }, }) // ✅ Replace statt Push (kein Zurück-Button Eintrag) const redirectAfterLogin = () => navigate({ to: '/dashboard', replace: true, }) // ❌ Compile-Fehler — Route existiert nicht // navigate({ to: '/user/$userId' }) → TypeScript meldet Fehler // ❌ Compile-Fehler — params fehlen // navigate({ to: '/users/$userId' }) → "params required" return <button onClick={goToUser}>Zum Profil</button> }

Type SafeLink-Komponente mit Active-State

// Prompt: "Navigation mit aktiven Link-States und preload on hover" import { Link } from '@tanstack/react-router' function NavBar() { return ( <nav> {/* activeProps werden bei aktiver Route angewendet */} <Link to="/" activeProps={{ className: 'nav-active' }} activeOptions={{ exact: true }} {/* Nur exakter Match */} > Home </Link> {/* Mit Parametern — vollständig typisiert */} <Link to="/users/$userId" params={{ userId: '42' }} search={{ tab: 'posts' }} activeProps={{ style: { fontWeight: 'bold' } }} > User Profil </Link> {/* Preload on hover — Route lädt Daten bevor geklickt */} <Link to="/dashboard" preload="intent"> Dashboard </Link> {/* Disabled State */} <Link to="/admin" disabled={!isAdmin}>Admin</Link> </nav> ) } // useMatchRoute — prüfen ob Route aktiv ist (ohne zu navigieren) import { useMatchRoute } from '@tanstack/react-router' function ConditionalUI() { const matchRoute = useMatchRoute() const isUserPage = matchRoute({ to: '/users/$userId' }) return isUserPage ? <UserSidebar /> : null }
preload="intent": TanStack Router lädt Route-Daten (Loaders) automatisch wenn der Nutzer mit der Maus über einen Link fährt — noch bevor er klickt. Das Ergebnis: gefühlte Sofort-Navigation bei komplexen Datenquellen.

Search Params: Type-Safe URL-State mit Zod

Search Params sind einer der häufigsten Pain Points in React-Apps — meistens als rohes useSearchParams() ohne Typ-Sicherheit implementiert. TanStack Router löst das mit Zod-Validierung direkt in der Route-Definition.

Type SafevalidateSearch mit Zod-Schema

// Prompt: "User-Liste mit Pagination, Filter und Sortierung als typsichere URL-Parameter" import { createFileRoute } from '@tanstack/react-router' import { z } from 'zod' import { zodSearchValidator } from '@tanstack/router-zod-adapter' const usersSearchSchema = z.object({ page: z.number().int().min(1).default(1), limit: z.number().int().min(10).max(100).default(20), filter: z.enum(['all', 'active', 'archived']).default('all'), sortBy: z.enum(['name', 'email', 'createdAt']).default('name'), sortOrder: z.enum(['asc', 'desc']).default('asc'), search: z.string().optional(), }) export const Route = createFileRoute('/users/')({ validateSearch: zodSearchValidator(usersSearchSchema), component: UsersPage, }) function UsersPage() { const { page, limit, filter, sortBy, sortOrder, search } = Route.useSearch() // Alle Typen sind korrekt: page ist number, filter ist 'all'|'active'|'archived' // Defaults wurden bereits angewendet — niemals undefined const navigate = useNavigate({ from: Route.fullPath }) const setPage = (newPage: number) => navigate({ search: (prev) => ({ ...prev, page: newPage }) // ← Partial Update: nur page ändert sich, Rest bleibt }) const setFilter = (f: 'all' | 'active' | 'archived') => navigate({ search: (prev) => ({ ...prev, filter: f, page: 1 }) // ← Beim Filtern: Seite zurücksetzen }) return ( <div> <FilterBar filter={filter} onFilterChange={setFilter} /> <UserTable users={users} sortBy={sortBy} sortOrder={sortOrder} /> <Pagination page={page} total={total} onPageChange={setPage} /> </div> ) }
URL als Single Source of Truth: Nutzer können Seiten mit exaktem Filterzustand bookmarken, Links teilen und mit dem Browser-Back-Button navigieren — ohne dass React-State verloren geht. Claude Code implementiert dieses Pattern in unter 2 Minuten.

Type SafeSearch Params zwischen Routen weitergeben

// Prompt: "Search Params aus Parent-Route in Child-Route verfügbar machen" // Parent: /search?q=claude&category=tools export const Route = createFileRoute('/search')({ validateSearch: zodSearchValidator(z.object({ q: z.string().default(''), category: z.string().optional(), })), }) // Child: /search/results — kann auf Parent Search Params zugreifen export const Route = createFileRoute('/search/results')({ component: () => { // useSearch mit from = Parent-Route const { q, category } = useSearch({ from: '/search' }) return <Results query={q} category={category} /> }, })

Loaders und beforeLoad: Datenladen vor dem Render

TanStack Router hat Loaders und Guards als First-Class-Feature — ähnlich wie Remix, aber mit vollständiger TypeScript-Integration und automatischem Caching.

LoaderDaten laden bevor die Komponente rendert

// Prompt: "User-Detail Route mit Loader — Daten laden, Fehlerbehandlung, Loading-UI" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/users/$userId')({ // Loader läuft VOR dem Render — Komponente bekommt garantiert Daten loader: async ({ params, context }) => { const user = await context.queryClient.ensureQueryData({ queryKey: ['users', params.userId], queryFn: () => fetchUser(params.userId), }) // Throw redirect für nicht-existierende User if (!user) throw redirect({ to: '/users', search: { error: 'not-found' } }) return { user } }, // Pending Component während Loader läuft pendingComponent: () => <UserDetailSkeleton />, // Error Component bei Loader-Fehler errorComponent: ({ error }) => ( <div className="error">Fehler: {error.message}</div> ), component: () => { // useLoaderData ist SYNCHRON — Daten sind garantiert da const { user } = Route.useLoaderData() // user-Typ wird automatisch aus dem Loader-Return inferiert return <UserDetail user={user} /> }, })

beforeLoadAuth Guards und Route Protection

// Prompt: "Auth Guard für alle /dashboard/* Routen mit Redirect zu Login" // 1. Router Context aufsetzen — Auth in Context injizieren import { createRouter, createRootRouteWithContext } from '@tanstack/react-router' import { QueryClient } from '@tanstack/react-query' interface RouterContext { queryClient: QueryClient auth: { isAuthenticated: boolean; user: User | null } } export const Route = createRootRouteWithContext<RouterContext>()({ component: RootLayout, }) // 2. Pathless Auth Layout — _auth.tsx (kein URL-Segment) export const Route = createFileRoute('/_auth')({ beforeLoad: ({ context, location }) => { // beforeLoad läuft BEVOR Loader und BEVOR Render if (!context.auth.isAuthenticated) { throw redirect({ to: '/login', search: { redirect: location.href, // ← Return URL merken }, }) } }, component: () => <Outlet />, }) // 3. Dashboard nutzt Auth-Guard automatisch (als Kind-Route) // src/routes/_auth/dashboard.tsx → /dashboard (Auth-geschützt) export const Route = createFileRoute('/_auth/dashboard')({ loader: async ({ context }) => { // context.auth.user ist hier garantiert nicht null (Guard hat geprüft) return await loadDashboardData(context.auth.user!.id) }, component: Dashboard, })
beforeLoad vs loader: beforeLoad läuft zuerst und kann redirecten oder Context anreichern. loader läuft danach und lädt Daten. Für Auth Guards immer beforeLoad verwenden — der Loader wird dann nur für authentifizierte Nutzer ausgeführt.

LoaderTanStack Query + Router: Das perfekte Duo

// Prompt: "TanStack Router Loaders mit React Query kombinieren für Prefetching" import { RouterProvider, createRouter } from '@tanstack/react-router' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const queryClient = new QueryClient() const router = createRouter({ routeTree, context: { queryClient }, // ← QueryClient in Router Context defaultPreload: 'intent', // ← Preload bei hover defaultPreloadStaleTime: 0, // ← Immer fresh beim Preload }) // In der Route — Query im Loader prefetchen export const Route = createFileRoute('/posts/$postId')({ loader: ({ context, params }) => context.queryClient.ensureQueryData({ queryKey: ['posts', params.postId], queryFn: () => fetchPost(params.postId), staleTime: 1000 * 60 * 5, // 5 Min Cache }), component: () => { const params = Route.useParams() // useQuery greift auf Cache zurück — KEIN erneuter Fetch nötig const { data: post } = useQuery({ queryKey: ['posts', params.postId], queryFn: () => fetchPost(params.postId), }) return <PostDetail post={post!} /> }, })

Nested Layouts und Code Splitting

Nested Layouts und automatisches Code Splitting sind zwei der leistungsstärksten Features von TanStack Router — und Claude Code implementiert beides ohne manuellen Aufwand.

LayoutNested Layouts mit Outlet

// Prompt: "Multi-Level Nested Layout: App → Dashboard → Settings Sidebar" // src/routes/__root.tsx — Globales Layout (Header/Footer für alle Seiten) export const Route = createRootRoute({ component: () => ( <AppShell> <GlobalHeader /> <Outlet /> {/* Dashboard-Layout oder normale Seiten */} <GlobalFooter /> </AppShell> ), }) // src/routes/_auth/dashboard.tsx — Dashboard-Layout export const Route = createFileRoute('/_auth/dashboard')({ component: () => ( <DashboardShell> <DashboardSidebar /> <main> <Outlet /> {/* Settings, Overview, etc. */} </main> </DashboardShell> ), }) // src/routes/_auth/dashboard/settings.tsx — Settings in Dashboard export const Route = createFileRoute('/_auth/dashboard/settings')({ component: () => ( <SettingsPage> <SettingsSidebar /> <Outlet /> {/* Profile, Security, etc. */} </SettingsPage> ), }) // URL: /dashboard/settings/profile // Rendert: AppShell → DashboardShell → SettingsPage → ProfileForm // Jede Ebene erhält NUR ihren Teil — kein Re-Render der Eltern
Granulares Re-Rendering: Bei Navigation von /dashboard/settings/profile zu /dashboard/settings/security re-rendert NUR die innerste Komponente. AppShell und DashboardShell bleiben stabil — das bedeutet: keine Flicker, keine verlorenen Scroll-Positionen.

Code SplittingAutomatisches Lazy Loading per Route

// Prompt: "Code Splitting für alle Dashboard-Routen — nur was gebraucht wird laden" // Option 1: .lazy.tsx Suffix — automatisches Splitting // src/routes/dashboard/analytics.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/dashboard/analytics')({ component: AnalyticsDashboard, // pendingComponent wird angezeigt während JS-Chunk lädt pendingComponent: () => <AnalyticsSkeleton />, }) // Option 2: Manuelles Code Splitting mit lazyRouteComponent import { lazyRouteComponent } from '@tanstack/react-router' export const Route = createFileRoute('/reports')({ // Loader lädt parallel zum JS-Chunk loader: () => loadReportData(), component: lazyRouteComponent( () => import('./ReportsPage'), 'ReportsPage' // Named Export ), }) // Option 3: Virtual Route Splitting (fortgeschritten) // Trennt Route-Definitionen von Komponenten für minimale Main-Bundle-Größe export const Route = createFileRoute('/heavy-feature')({ loader: heavyLoader, // Bleibt im Main Bundle }).lazy(() => import('./heavy-feature.lazy').then(d => d.Route))

Pending UILoading States und Error Boundaries pro Route

// Prompt: "Granulare Loading-States: Skeleton während Loader läuft, Error Fallback" import { createFileRoute, Outlet } from '@tanstack/react-router' export const Route = createFileRoute('/users/')({ loader: fetchUsers, // Zeigt Skeleton während Loader noch nicht fertig ist pendingComponent: () => ( <div className="users-grid"> {[...Array(6)].map((_, i) => <UserCardSkeleton key={i} />)} </div> ), pendingMs: 200, // ← Skeleton erst nach 200ms zeigen (verhindert Flicker) pendingMinMs: 500, // ← Skeleton mindestens 500ms zeigen (verhindert Flicker) // Error Boundary pro Route — kein globaler Crash errorComponent: ({ error, reset }) => ( <div className="error-card"> <h3>Fehler beim Laden</h3> <p>{error.message}</p> <button onClick={reset}>Nochmal versuchen</button> </div> ), component: () => { const users = Route.useLoaderData() return <UsersGrid users={users} /> }, }) // notFoundComponent — eigene 404 pro Route-Bereich export const Route = createFileRoute('/blog')({ notFoundComponent: () => <BlogNotFound />, component: () => <Outlet />, })
pendingMs + pendingMinMs: Diese zwei Parameter verhindern "Flicker" — zeige den Skeleton erst nach 200ms (kurze Requests zeigen gar keinen Loader) und mindestens 500ms (verhindert den unangenehmen kurzen Aufblitz). Claude Code setzt diese Werte standardmäßig.

Migration von React Router zu TanStack Router

Claude Code kann bestehende React-Router-Projekte migrieren — mit einem strukturierten Prompt und schrittweisem Vorgehen ohne Breaking Changes.

MigrationSchritt-für-Schritt Migrationspfad

  1. Abhängigkeiten austauschen: react-router-dom deinstallieren, @tanstack/react-router + Vite Plugin installieren
  2. Route-Dateien erstellen: Für jede bestehende Route eine Datei in routes/ anlegen — Claude Code mappt alle <Route path="..."> automatisch
  3. useParams migrieren: useParams()Route.useParams() — Claude Code findet alle Stellen und fügt Typen hinzu
  4. useNavigate migrieren: navigate('/users/42')navigate({ to: '/users/$userId', params: { userId: '42' } })
  5. Search Params typisieren: useSearchParams()validateSearch mit Zod-Schema definieren
  6. Loader extrahieren: Data Fetching aus Komponenten in Route loader verschieben — Suspense-Boundary entfällt
  7. Guards migrieren: <PrivateRoute>-Wrapper → Pathless Layout mit beforeLoad
Claude Code Migration Prompt: "Migriere diese React Router v6 Konfiguration zu TanStack Router mit File-Based Routing. Erstelle alle Route-Dateien, füge Typen hinzu und behalte die gleiche URL-Struktur." — Claude analysiert die bestehende Routing-Config und erstellt alle Dateien mit korrekten Typen.

DevtoolsTanStack Router Devtools nutzen

// Prompt: "Devtools für Development einbinden, in Production ausblenden" import { Suspense, lazy } from 'react' import { createRootRoute, Outlet } from '@tanstack/react-router' // Lazy Import — Devtools nur in Development bundeln const TanStackRouterDevtools = process.env.NODE_ENV === 'production' ? () => null : lazy(() => import('@tanstack/router-devtools').then((res) => ({ default: res.TanStackRouterDevtools, })) ) export const Route = createRootRoute({ component: () => ( <> <Outlet /> <Suspense> <TanStackRouterDevtools position="bottom-right" initialIsOpen={false} /> </Suspense> </> ), })
Was die Devtools zeigen: Alle aktiven Routes, Match-Hierarchie, Loader-Status, Cache-Zustand, Search Params live — unverzichtbar beim Debugging von komplexen Nested Layouts und Loader-Chains.

100% Type-Safe

Routen, Parameter, Search Params und Loader-Daten — vollständig TypeScript-inferiert ohne manuelle Typ-Annotationen.

File-Based Routing

Vite Plugin generiert automatisch den Route-Tree — keine manuelle Router-Konfiguration mehr.

Loader + beforeLoad

Daten laden und Auth Guards vor dem Render — keine Suspense-Boundary mehr, kein Loading-State in Komponenten.

Search Param Zod

URL-State mit Zod validieren — Defaults, Typen, Coercion direkt in der Route-Definition.

Code Splitting

.lazy.tsx Suffix für automatisches per-Route Code Splitting — kleineres Initial Bundle ohne Konfiguration.

Preload on Intent

Loader starten beim Hover — Navigation fühlt sich sofortig an, auch bei komplexen Datenbankabfragen.

Claude Code für dein React-Projekt nutzen

TanStack Router, Type-Safe APIs, Zod-Validierung — Claude Code kennt alle modernen Patterns und implementiert sie in Minuten statt Stunden. Teste 14 Tage kostenlos.

Jetzt kostenlos starten →

Fazit: TanStack Router ist der neue Standard

TanStack Router löst das größte Problem mit React-Routing: fehlendes Type-System. Jede Route, jeder Parameter, jeder Search Param ist vollständig von TypeScript verstanden — Refactoring ist sicher, Tippfehler werden sofort erkannt, IDE-Autocompletion funktioniert überall.

Claude Code generiert TanStack-Router-Setups mit File-Based Routing, Zod-validierten Search Params, Auth Guards via beforeLoad und automatischem Code Splitting — vollständig konfiguriert und typsicher, ohne manuellen Boilerplate. Für neue React-Projekte ohne Server-Rendering ist TanStack Router 2026 die erste Wahl.