Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe Route Prefetching im TanStack Ship Dashboard implementiert, nachdem ich bemerkte, dass die Navigation zur Analytics-Seite einen weißen Bildschirm zeigte, während TanStack Query die initialen Daten lud. Die Lösung waren zwei Zeilen Konfiguration: eine Preload-Funktion auf der Route und ein Data Loader, der vor dem Rendern der Komponente ausgeführt wurde. Die Seite ging von 400ms wahrgenommener Ladezeit auf unter 100ms. Der beforeLoad-Hook für Auth Guards war das Erste, was ich hinzugefügt habe – noch bevor ich die Login-Seite ausgeliefert habe. Dieser Guide behandelt beide Patterns mit dem exakten Code aus der Produktion.
Verifizierte Quellen: TanStack Router beforeLoad-Dokumentation · TanStack Router Preloading · TanStack Router Code Splitting Letzte Aktualisierung: 18. September 2026 · Changelog
TL;DR: Drei TanStack Router Patterns, die Navigation Loading States eliminieren: (1) beforeLoad für Auth Guards – Weiterleitung, bevor die Komponente rendert; (2) preload für Daten – Daten werden geholt, bevor die Navigation abgeschlossen ist; (3) Automatisches Code Splitting über den Route Tree – keine Konfiguration nötig. Jedes Pattern lässt sich in unter 30 Minuten implementieren.
Die drei Navigationsprobleme
TanStack Router löst drei Navigationsprobleme, mit denen andere Router schlecht umgehen:
- Auth Guards: Authentifizierte Benutzer umleiten, bevor die geschützte Seite rendert – nicht danach
- Data Preloading: Routendaten holen, bevor der Benutzer klickt, damit Navigation sich instant anfühlt
- Code Splitting: Nur den Code für die besuchte Route laden – nicht die gesamte App
Pattern 1: beforeLoad für Auth Guards
Der beforeLoad-Hook ist der richtige Ort für Auth-Checks. Er wird vor dem Loader und der Komponente der Route ausgeführt – wenn Sie von beforeLoad aus weiterleiten, wird keiner von beiden ausgeführt.
import { Route } from './routeTree'
import { redirect } from '@tanstack/react-router'
// Auth-Check einmal definieren
async function requireAuth() {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login', search: { redirectTo: window.location.pathname } })
}
return user
}
// Auf jede geschützte Route anwenden
const dashboardRoute = new Route({
getParentRoute: () => rootRoute,
path: '/dashboard',
beforeLoad: async () => {
await requireAuth()
},
loader: async ({ context }) => {
// context.user ist hier verfügbar, weil beforeLoad es gesetzt hat
const data = await fetchDashboardData(context.user.id)
return { data }
},
component: DashboardPage,
})
Der Schlüssel: beforeLoad kann Context setzen, den Loader und Komponenten konsumieren. User in beforeLoad setzen, im Loader lesen:
const dashboardRoute = new Route({
path: '/dashboard',
beforeLoad: async ({ context }) => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login' })
}
return { user } // Das wird Teil von context
},
loader: async ({ context }) => {
// context.user ist hier typisiert und verfügbar
return fetchDashboardData(context.user.id)
},
})
TypeScript-Inferenz: Der context.user-Typ im Loader wird aus dem abgeleitet, was beforeLoad zurückgibt. Keine manuelle Typisierung nötig.
Pattern 2: Route Preloading
Preloading führt Data Fetcher oder Code aus, bevor die Navigation abgeschlossen ist. Das Ergebnis: Navigation fühlt sich instant an, weil die Daten bereits im Cache sind.
Preload-Funktionen
import { Route, preloadQuery } from '@tanstack/react-router'
import { queryClient } from './queryClient'
const productsRoute = new Route({
getParentRoute: () => rootRoute,
path: '/products',
loader: async () => {
return queryClient.ensureQueryData({
queryKey: ['products'],
queryFn: () => fetchProducts(),
})
},
component: ProductsPage,
})
// Die Preload-Funktion wird aufgerufen, bevor die Navigation abgeschlossen ist
const productsLink = () => {
const preload = () => productsRoute.load()
return (
<Link
to={productsRoute.fullPath}
onMouseEnter={preload}
onFocus={preload}
>
Products
</Link>
)
}
onMouseEnter feuert 50–200ms vor dem Klick. Der Loader läuft in diesem Fenster. Wenn der Benutzer klickt, sind die Daten im TanStack Query Cache.
Prefetch mit Route Tree
Der Route Tree von TanStack Router kann ganze Subtrees prefetchen:
// Alle Daten für die Dashboard-Route und ihre Kinder prefetchen
dashboardRoute.preload({
// Alle Kind-Routen mit einbeziehen
includeDescendants: true,
})
Für ein Dashboard mit mehreren Tabs werden so alle Tab-Daten vorgeladen, wenn der Benutzer über den Dashboard-Link hovert.
Preload-Timing
Der Preload-Hook integriert sich in TanStack Querys Background Refetch:
const analyticsRoute = new Route({
path: '/analytics',
loader: async ({ preload }) => {
// Wenn Preload aufgerufen wurde, könnten Daten stale sein – trotzdem neu holen
if (preload) {
return queryClient.fetchQuery({
queryKey: ['analytics'],
queryFn: () => fetchAnalytics(),
staleTime: 0, // Nach Preload immer frisch
})
}
// Normale Navigation: Cache nutzen, wenn frisch
return queryClient.ensureQueryData({
queryKey: ['analytics'],
queryFn: () => fetchAnalytics(),
staleTime: 1000 * 60 * 5, // 5 Minuten
})
},
})
Pattern 3: Automatisches Code Splitting
TanStack Router erledigt Code Splitting automatisch über den Route Tree. Keine Webpack-Konfiguration nötig.
Lazy Route Components
import { Route, lazy } from '@tanstack/react-router'
const heavyRoute = new Route({
getParentRoute: () => rootRoute,
path: '/reports',
// lazy() teilt die Komponente automatisch auf
component: lazy(() => import('./ReportsPage')),
})
const settingsRoute = new Route({
getParentRoute: () => rootRoute,
path: '/settings',
component: lazy(() => import('./SettingsPage')),
})
Die Komponente wird nur geladen, wenn die Route matcht. Jede Route ist ein separater Chunk im Bundle. Kein React.lazy, keine Suspense-Boundary-Konfiguration – der Router kümmert sich darum.
Lazy mit Prefetch
Lazy mit Preload kombinieren für instant-fühlende Navigation:
const reportsRoute = new Route({
path: '/reports',
component: lazy({
loader: () => import('./ReportsPage'),
// Komponente prefetchen, wenn die Route matcht
preload: true,
}),
})
Mit preload: true beginnt der Download des Komponenten-Chunks, sobald die Route gematcht wird – noch bevor die Komponente rendert.
Bundle-Analyse
Nach dem Hinzufügen von Lazy Routes das Bundle prüfen:
npx vite-bundle-visualizer
Sie sollten separate Chunks für jede Lazy Route sehen. Ein typisches TanStack Router + lazy Setup:
| Chunk | Ungefähre Größe | Ladezeit |
|---|---|---|
| Main Bundle (Shell + Root) | ~45KB | Sofort |
| Dashboard Chunk | ~22KB | Bei Navigation |
| Reports Chunk | ~38KB | Bei Navigation |
| Analytics Chunk | ~51KB | Bei Navigation |
Die Shell lädt zuerst. Jede Route lädt bei Bedarf. Ein Benutzer, der nur das Dashboard besucht, lädt nie den Analytics Chunk.
Alle drei kombiniert: Das geschützte Dashboard
import { Route, lazy, redirect } from '@tanstack/react-router'
import { queryClient } from './queryClient'
const dashboardRoute = new Route({
getParentRoute: () => rootRoute,
path: '/dashboard',
beforeLoad: async ({ preload }) => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login' })
}
return { user }
},
loader: async ({ context, preload }) => {
// Wenn prefetched, sofort nutzen. Sonst warten.
return queryClient.ensureQueryData({
queryKey: ['dashboard', context.user.id],
queryFn: () => fetchDashboard(context.user.id),
staleTime: preload ? 0 : 1000 * 60 * 2,
})
},
component: lazy(() => import('./DashboardPage')),
})
// Navigationskomponente mit Preload bei Hover
function DashboardLink() {
return (
<Link
to={dashboardRoute.fullPath}
onMouseEnter={() => dashboardRoute.load()}
onFocus={() => dashboardRoute.load()}
>
Dashboard
</Link>
)
}
Das Ergebnis: beforeLoad läuft, bevor die Route geladen wird → Auth-Check → Loader läuft mit Context → Komponente rendert mit Daten aus dem Cache. Navigation fühlt sich instant an.
Performance-Ergebnisse aus der Produktion
Nach Implementierung aller drei Patterns im TanStack Ship Dashboard:
| Metrik | Vorher | Nachher |
|---|---|---|
| Wahrgenommene Ladezeit Dashboard-Navigation | 380ms | 60ms |
| Wahrgenommene Ladezeit Analytics-Navigation | 520ms | 80ms |
| Main Bundle Größe | 82KB | 45KB |
| Dashboard Chunk Größe | — | 22KB |
| Auth Guard Flash (nicht authentifiziert) | Keiner | Keiner |
Die Verbesserung um 320ms bei der Dashboard-Navigation kommt vollständig von ensureQueryData – die Daten wurden bei Hover prefetched. Der Benutzer klickte auf einen warmen Link.
Weiterführende Lektüre: React Router V7 vs. TanStack Router 2026 · TanStack Router vs. React Router 2026 · React Router vs. TanStack Router SEO Optimized
Production-Checkliste
- Auth Guards in beforeLoad – nicht in der Komponente
useEffect - Preload auf wichtigen Navigations-Links –
onMouseEnterin der Hauptnavigation - Lazy Components auf schweren Routen –
/reports,/analytics,/settings - Bundle-Analyse nach Lazy-Setup – verifizieren, dass Chunks separat sind
- beforeLoad Context typisiert – TypeScript-Inferenz im Loader prüfen
TanStack Ship wird mit TanStack Router vorkonfiguriert ausgeliefert – inklusive Auth Guards, Lazy Route Components und TanStack Query Integration. Siehe die TanStack Router Konfiguration und die vollständige Feature-Liste.