TanStack Router beforeLoad, Preload & Code Splitting: Production Patterns Guide

TanStack Router beforeLoad, preload, route prefetching und Code Splitting. beforeLoad für Auth Guards, Data Preloading für instant Navigation und Automatic Code Splitting ohne Webpack.

Huifer
Huifer
18. September 20269 min read
Auch verfügbar auf:English · 中文

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:

  1. Auth Guards: Authentifizierte Benutzer umleiten, bevor die geschützte Seite rendert – nicht danach
  2. Data Preloading: Routendaten holen, bevor der Benutzer klickt, damit Navigation sich instant anfühlt
  3. 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.

typescript
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:

typescript
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

typescript
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:

typescript
// 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:

typescript
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

typescript
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:

typescript
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:

bash
npx vite-bundle-visualizer

Sie sollten separate Chunks für jede Lazy Route sehen. Ein typisches TanStack Router + lazy Setup:

ChunkUngefähre GrößeLadezeit
Main Bundle (Shell + Root)~45KBSofort
Dashboard Chunk~22KBBei Navigation
Reports Chunk~38KBBei Navigation
Analytics Chunk~51KBBei 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

typescript
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:

MetrikVorherNachher
Wahrgenommene Ladezeit Dashboard-Navigation380ms60ms
Wahrgenommene Ladezeit Analytics-Navigation520ms80ms
Main Bundle Größe82KB45KB
Dashboard Chunk Größe—22KB
Auth Guard Flash (nicht authentifiziert)KeinerKeiner

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 – onMouseEnter in 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.