Loader: Daten mit der Route laden
Bisher laden unsere Komponenten ihre Daten selbst — mit useQuery nach dem ersten Render. Das bedeutet: Erst rendert die Komponente (mit Ladezustand), dann startet die Anfrage, dann rendert sie mit den Daten. React Router bietet eine Alternative: Loader starten die Datenanfrage schon während der Navigation — bevor die Komponente überhaupt rendert.
Loader definieren
Abschnitt betitelt „Loader definieren“Ein Loader ist eine normale async-Funktion, die direkt an der Route-Definition hängt:
import { createBrowserRouter, RouterProvider } from 'react-router'import TodoListe, { todosLoader } from './TodoListe'import TodoDetail, { todoLoader } from './TodoDetail'
const router = createBrowserRouter([ { path: '/', element: <App />, children: [ { index: true, element: <TodoListe />, loader: todosLoader, }, { path: 'todos/:id', element: <TodoDetail />, loader: todoLoader, }, ], },])React Router ruft den Loader auf, sobald der Nutzer zu dieser Route navigiert — parallel zum Laden des JavaScript-Bundles der Komponente, noch vor dem ersten Render.
Loader schreiben
Abschnitt betitelt „Loader schreiben“Der Loader lebt in derselben Datei wie die Komponente und wird mitexportiert:
import { useLoaderData, Link } from 'react-router'
type Todo = { id: number; title: string; completed: boolean; userId: number }
export async function todosLoader(): Promise<Todo[]> { const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=20') if (!res.ok) throw new Error(`HTTP-Fehler: ${res.status}`) return res.json()}
export default function TodoListe() { const todos = useLoaderData() as Todo[]
return ( <ul> {todos.map(todo => ( <li key={todo.id}> <Link to={`/todos/${todo.id}`}>{todo.title}</Link> </li> ))} </ul> )}useLoaderData() gibt zurück, was der Loader zurückgegeben hat — die Komponente rendert direkt mit Daten, ohne Ladezustand.
Loader mit URL-Parametern
Abschnitt betitelt „Loader mit URL-Parametern“Für dynamische Routen bekommt der Loader ein params-Objekt mit den URL-Parametern:
import { useLoaderData, Link } from 'react-router'import type { LoaderFunctionArgs } from 'react-router'
type Todo = { id: number; title: string; completed: boolean; userId: number }
export async function todoLoader({ params }: LoaderFunctionArgs): Promise<Todo> { const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${params.id}`) if (!res.ok) throw new Error(`HTTP-Fehler: ${res.status}`) return res.json()}
export default function TodoDetail() { const todo = useLoaderData() as Todo
return ( <div> <Link to="/">← Zurück</Link> <h2>{todo.title}</h2> <p>Status: {todo.completed ? 'Erledigt' : 'Offen'}</p> </div> )}Fehler im Loader — zum Beispiel ein 404 — werden automatisch von errorElement aufgefangen, genau wie Routing-Fehler.
TypeScript
Abschnitt betitelt „TypeScript“useLoaderData() gibt unknown zurück, weil React Router zur Laufzeit nicht weiß, welcher Loader für die aktuelle Route zuständig ist. Der Cast auf den erwarteten Typ ist nötig:
const todos = useLoaderData() as Todo[]const todo = useLoaderData() as TodoFür vollständige Typsicherheit ohne Cast bietet React Router v7 einen Code-Generator, der typisierte useLoaderData-Hooks aus der Router-Konfiguration erzeugt. Für die meisten Anwendungen reicht der Cast.
Loader vs. useQuery
Abschnitt betitelt „Loader vs. useQuery“Beide Ansätze funktionieren — sie lösen aber unterschiedliche Probleme:
| Loader | useQuery | |
|---|---|---|
| Wann startet die Anfrage | Während der Navigation | Nach dem ersten Render |
| Ladezustand in der Komponente | Nein — Daten sind sofort da | Ja — isLoading nötig |
| Caching | Nein (pro Navigation neu) | Ja — Cache, Background Refetch |
| Refetching | Nein | Ja |
| Geeignet für | Daten, die vor dem Render da sein müssen | Daten, die nach dem Render laden dürfen |
| SSR / Streaming | Ja | Nein (client-only) |
Loader sind die bessere Wahl, wenn die Komponente ohne Daten sinnlos ist und kein Ladezustand gezeigt werden soll — zum Beispiel für die initiale Seitenstruktur oder wenn Server-Rendering relevant ist.
useQuery ist die bessere Wahl, wenn Daten im Hintergrund aktuell gehalten werden sollen, wenn gecacht werden soll oder wenn die Komponente auch ohne Daten sinnvoll rendert — zum Beispiel mit einem Skeleton.
In der Praxis werden beide häufig kombiniert: Der Loader lädt kritische Daten für die erste Darstellung, useQuery übernimmt dynamische oder oft wechselnde Daten innerhalb der Komponente.