Nahtloses Server-Zustandsmanagement in Next.js mit TanStack Query
Ethan Miller
Product Engineer · Leapcell

Einleitung
In der modernen Webentwicklung, insbesondere in Single Page Applications (SPAs) und serverseitig gerenderten (SSR) Frameworks wie Next.js, ist die effiziente Verwaltung von serverseitigen Daten von größter Bedeutung. Während Client-seitige Zustandsverwaltungsbibliotheken den UI-Zustand umfassend abdecken, führen die Herausforderungen beim Abrufen, Zwischenspeichern, Synchronisieren und Aktualisieren von asynchronen Serverdaten oft zu Standardcode, komplexer Logik und inkonsistenten Benutzererfahrungen. Denken Sie an die Frustration des Benutzers, wenn Daten veraltet sind, Ladeindikatoren fehlen oder Mutationen zu optimistischen Updates führen, die schwer rückgängig zu machen sind. Diese Szenarien verdeutlichen eine kritische Lücke in unseren traditionellen Ansätzen. Hier setzt TanStack Query, ehemals bekannt als React Query, an und bietet eine elegante Lösung, um diese Komplexitäten zu abstrahieren und unsere Datenverwaltungsstrategie in Next.js-Anwendungen zu verbessern.
Grundlegende Konzepte verstehen
Bevor wir uns mit der praktischen Implementierung befassen, wollen wir die grundlegenden Konzepte, die TanStack Query untermauern, klar verstehen:
Serverzustand
Im Gegensatz zum Client-Zustand, der vom Frontend besessen und kontrolliert wird, befindet sich der Serverzustand auf einem entfernten Server. Er ist asynchron, muss abgerufen werden, kann persistent sein (und somit von anderen geändert werden) und wird oft mit der Zeit "veraltet". Beispiele hierfür sind Benutzerprofile, Produktlisten oder Bestelldetails, die von einer API abgerufen werden.
Caching
TanStack Query verwendet einen intelligenten, automatischen Caching-Mechanismus für Serverdaten. Wenn Sie Daten abrufen, werden diese in einem Query-Cache gespeichert. Nachfolgende Anfragen für dieselben Daten treffen oft zuerst auf den Cache, liefern sofortiges UI-Feedback und reduzieren Netzwerkanfragen, was zu einer schnelleren und reaktionsschnelleren Anwendung führt.
Stale-While-Revalidate (SWR)
Dies ist eine leistungsstarke Caching-Strategie, bei der die Benutzeroberfläche sofort zwischengespeicherte (veraltete) Daten anzeigt, während gleichzeitig im Hintergrund die aktuellsten Daten erneut abgerufen werden. Sobald die neuen Daten eintreffen, werden der Cache und die Benutzeroberfläche aktualisiert, wodurch sichergestellt wird, dass der Benutzer schnell etwas sieht, auch wenn es nicht das absolut Neueste ist, und schließlich die aktuellsten Informationen erhält.
Query Keys
Dies sind eindeutige Bezeichner, die von TanStack Query verwendet werden, um verschiedene Teile des Serverzustands in seinem Cache zu verwalten und zu unterscheiden. Sie sind typischerweise Arrays, die eine hierarchische Strukturierung und präzise Invalidierung ermöglichen. Zum Beispiel könnte ['todos']
alle Todos darstellen, während ['todos', todoId]
ein bestimmtes Todo darstellt.
Query Invalidierung
Der Prozess der Markierung von zwischengespeicherten Daten als "veraltet", damit TanStack Query weiß, dass sie beim nächsten Zugriff erneut abgerufen werden müssen. Dies ist entscheidend nach Mutationen (z. B. dem Erstellen, Aktualisieren oder Löschen von Daten), um sicherzustellen, dass die Benutzeroberfläche den neuesten Serverzustand widerspiegelt.
Optimistische Updates
Eine Technik, bei der die Benutzeroberfläche sofort nach dem Senden einer Mutationsanfrage an den Server aktualisiert wird, bevor der Server die Änderung bestätigt hat. Dies gibt dem Benutzer sofortiges Feedback und lässt die Anwendung schneller wirken. Wenn die Serveranfrage fehlschlägt, kann die Benutzeroberfläche in ihren vorherigen Zustand zurückversetzt werden.
TanStack Query in Next.js
TanStack Query eignet sich hervorragend für die Verwaltung von Serverzuständen und bietet deklarative APIs zum Abrufen, Zwischenspeichern, Synchronisieren und Aktualisieren von Daten. Wenn es mit Next.js integriert wird, bietet es eine robuste Lösung für einen nahtlosen Datenfluss und profitiert insbesondere von den Datenabruffähigkeiten von Next.js wie getServerSideProps
oder getStaticProps
für die anfängliche Datenhydrierung.
Kernprinzipien und Vorteile
- Eliminiert manuelle Caching-Logik: Sagen Sie Lebewohl zum Schreiben Ihrer eigenen Caching-Logik. TanStack Query übernimmt dies alles automatisch, einschließlich der Garbage Collection für ungenutzte Daten.
- Löst Herausforderungen bei der Datensynchronisation: Es stellt sicher, dass Ihre Benutzeroberfläche mit Ihren Backend-Daten synchron bleibt, selbst nach Mutationen, Neufokussierung des Fensters oder Wiederherstellung der Netzwerkverbindung.
- Reduziert Boilerplate-Code: Vereinfachter Datenabruf mit Hooks wie
useQuery
unduseMutation
reduziert die repetitive Code für Ladezustände, Fehlerbehandlung und Datenabruf drastisch. - Hervorragende Entwicklererfahrung: Umfassende Entwickler-Tools bieten Einblicke in den Query-Cache und erleichtern das Debugging und das Verständnis des Datenflusses erheblich.
- Leistungsverbesserungen: Aggressives Caching und Hintergrund-Refetching sorgen für eine schnellere Benutzererfahrung.
Implementierungsbeispiel
Lassen Sie uns veranschaulichen, wie TanStack Query in eine Next.js-Anwendung integriert wird.
Installieren Sie zuerst die benötigten Pakete:
npm install @tanstack/react-query @tanstack/react-query-devtools # oder yarn add @tanstack/react-query @tanstack/react-query-devtools
Als Nächstes richten Sie den QueryClientProvider
in Ihrer _app.tsx
ein, um den QueryClient
in Ihrer gesamten Anwendung verfügbar zu machen:
// pages/_app.tsx import type { AppProps } from 'next/app'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { useState } from 'react'; function MyApp({ Component, pageProps }: AppProps) { const [queryClient] = useState(() => new QueryClient()); return ( <QueryClientProvider client={queryClient}> <Component {...pageProps} /> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ); } export default MyApp;
Nun erstellen wir eine einfache Komponente, um eine Liste von Todos abzurufen und anzuzeigen.
// components/TodoList.tsx import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; interface Todo { id: number; title: string; completed: boolean; } // Mimik einer API-Abfrage const fetchTodos = async (): Promise<Todo[]> => { const res = await fetch('/api/todos'); // Gehen wir davon aus, dass Sie eine API-Route haben if (!res.ok) { throw new Error('Failed to fetch todos'); } return res.json(); }; const addTodo = async (todoTitle: string): Promise<Todo> => { const res = await fetch('/api/todos', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ title: todoTitle, completed: false }), }); if (!res.ok) { throw new Error('Failed to add todo'); } return res.json(); }; const TodoList: React.FC = () => { const queryClient = useQueryClient(); const { data: todos, isLoading, isError, error } = useQuery<Todo[], Error>({ queryKey: ['todos'], queryFn: fetchTodos, }); const { mutate: addATodo, isLoading: isAddingTodo } = useMutation< Todo, Error, string >({ mutationFn: addTodo, onSuccess: () => { // Die 'todos'-Query ungültig machen, um die Liste nach einer erfolgreichen Hinzufügung erneut abzurufen queryClient.invalidateQueries({ queryKey: ['todos'] }); }, }); if (isLoading) return <div>Todos werden geladen...</div>; if (isError) return <div>Fehler: {error?.message}</div>; const handleAddTodo = () => { const newTodoTitle = prompt('Geben Sie den Titel der neuen Todo ein:'); if (newTodoTitle) { addATodo(newTodoTitle); } }; return ( <div> <h1>Todo-Liste</h1> <ul> {todos?.map((todo) => ( <li key={todo.id}> {todo.title} - {todo.completed ? 'Erledigt' : 'Ausstehend'} </li> ))} </ul> <button onClick={handleAddTodo} disabled={isAddingTodo}> {isAddingTodo ? 'Wird hinzugefügt...' : 'Todo hinzufügen'} </button> </div> ); }; export default TodoList;
Und Ihre Next.js-API-Route (z. B. pages/api/todos.ts
):
// pages/api/todos.ts import { NextApiRequest, NextApiResponse } from 'next'; let todos = [ { id: 1, title: 'Nächsten.js lernen', completed: false }, { id: 2, title: 'TanStack Query erkunden', completed: false }, ]; let nextId = 3; export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method === 'GET') { res.status(200).json(todos); } else if (req.method === 'POST') { const { title, completed } = req.body; const newTodo = { id: nextId++, title, completed }; todos.push(newTodo); res.status(201).json(newTodo); } else { res.setHeader('Allow', ['GET', 'POST']); res.status(405).end(`Methode ${req.method} nicht erlaubt`); } }
In diesem Beispiel:
useQuery
ruft die Liste der Todos mithilfe derfetchTodos
-Funktion und['todos']
als eindeutigen Schlüssel ab. Sie liefertdata
,isLoading
,isError
und denerror
-Zustand out-of-the-box.useMutation
wird für dieaddTodo
-Operation verwendet. Nach erfolgreichem Abschluss (onSuccess
) wirdqueryClient.invalidateQueries
mitqueryKey: ['todos']
aufgerufen. Dies teilt TanStack Query mit, dass dietodos
-Daten nun veraltet sind und beim nächsten Zugriff erneut abgerufen werden müssen, wodurch sichergestellt wird, dass unsere Liste immer den neuesten Serverzustand widerspiegelt.
Fortgeschrittene Muster: Startdaten-Hydrierung mit Next.js
Für SEO und schnellere anfängliche Seitenaufrufe verwendet Next.js oft serverseitiges Rendering (SSR) oder Static Site Generation (SSG). TanStack Query kann den Client-seitigen Cache mit auf dem Server abgerufenen Daten hydrieren.
// pages/index.tsx import { dehydrate, QueryClient, Hydrate } from '@tanstack/react-query'; import { GetServerSideProps } from 'next'; import TodoList from '../components/TodoList'; interface Todo { id: number; title: string; completed: boolean; } const fetchTodos = async (): Promise<Todo[]> => { const res = await fetch('http://localhost:3000/api/todos'); // Absolute URL auf dem Server verwenden if (!res.ok) { throw new Error('Failed to fetch todos'); } return res.json(); }; export const getServerSideProps: GetServerSideProps = async () => { const queryClient = new QueryClient(); // Daten auf dem Server vorab abrufen await queryClient.prefetchQuery({ queryKey: ['todos'], queryFn: fetchTodos, }); return { props: { dehydratedState: dehydrate(queryClient), }, }; }; export default function HomePage({ dehydratedState }: { dehydratedState: unknown }) { return ( <Hydrate state={dehydratedState}> <TodoList /> </Hydrate> ); }
Hier ruft getServerSideProps
die Todos auf dem Server ab, erstellt einen dehydratedState
und übergibt ihn an den Client. Die Hydrate
-Komponente injiziert diese Daten dann in den Client-seitigen TanStack Query-Cache, sodass TodoList
die Daten beim anfänglichen Rendern nicht erneut abrufen muss. Dies bietet die Vorteile von SSR (schnelles initiales Rendering) in Kombination mit dem leistungsstarken Client-seitigen Caching und der Synchronisation von TanStack Query.
Fazit
TanStack Query verändert grundlegend, wie wir Serverzustandsmanagement in React-Anwendungen angehen, insbesondere in komplexen Next.js-Projekten. Durch die Automatisierung von Abruf-, Caching- und Datensynchronisationsprozessen reduziert es den Boilerplate-Code drastisch, verbessert die Leistung und steigert die Entwickler- und Benutzererfahrung. Seine intuitive API in Kombination mit leistungsstarken Funktionen wie Query-Invalidierung und optimistischen Updates macht es zu einem unverzichtbaren Werkzeug für den Aufbau robuster und reaktiver Webanwendungen. Nutzen Sie TanStack Query, um ein neues Maß an Effizienz und Eleganz bei der Verwaltung der Daten Ihrer Anwendung zu erschließen. Es verwandelt Datenabrufe von einer Herausforderung in ein gelöstes Problem.