React & State

React Query v5 mit Claude Code:
Server State Management 2026

TanStack Query v5 hat Server State Management in React grundlegend verandert. Mit Claude Code lassen sich useQuery, useMutation und Infinite Queries schnell und typsicher implementieren — inklusive Optimistic Updates, Suspense und SSR-Integration fur Next.js App Router.

Inhalt dieses Artikels

  1. QueryClient Setup & useQuery
  2. useMutation & Cache-Invalidierung
  3. Infinite Queries & Pagination
  4. Optimistic Updates & Rollback
  5. Suspense & Error Boundaries
  6. Prefetching & SSR-Integration

Wer 2026 mit React produktiv entwickelt, kommt an TanStack Query nicht vorbei. Die Bibliothek hat sich als de-facto-Standard fur Server State Management durchgesetzt — und v5 bringt grundlegende API-Verbesserungen: konsistentere Typen, ein einheitliches Objekt-API fur Hooks, natives Suspense-Support und deutlich bessere DevTools.

In diesem Artikel zeigen wir, wie Claude Code beim Aufbau einer vollstandigen React Query Architektur unterstutzt: von QueryClient-Konfiguration uber typsichere Query-Key-Factories bis hin zu SSR-Hydration mit Next.js App Router. Jeder Abschnitt enthalt realistische TypeScript-Beispiele, die Claude Code in ahnlicher Form vorschlagen wurde.

Was du lernst: Wie Claude Code die React Query v5 Implementierung beschleunigt — mit realistischen TypeScript-Beispielen, Best Practices und haufigen Fallstricken, die der KI-Assistent direkt erkennt und vermeidet.
React Query v5 Breaking Changes: In v5 akzeptieren alle Hooks nur noch ein einzelnes Konfigurationsobjekt (kein separater zweiter Parameter mehr). cacheTime wurde zu gcTime umbenannt. isLoading verhalt sich nun anders bei deaktivierten Queries. Claude Code kennt diese Anderungen und generiert sofort kompatiblen Code.

1. QueryClient Setup & useQuery

Die Basis jeder React Query Anwendung ist ein korrekt konfigurierter QueryClient. Claude Code generiert nicht nur den Boilerplate — es wahlt sinnvolle Defaults fur staleTime, gcTime und Retry-Verhalten basierend auf dem Projektkontext.

QueryClient mit sinnvollen Defaults

Claude Code empfiehlt eine zentrale Konfigurationsdatei, die den QueryClient mit Projekt-spezifischen Defaults initialisiert. Der entscheidende Vorteil: Man definiert das Verhalten einmal und uberschreibt es nur an den Stellen, wo es wirklich notig ist.

// lib/query-client.ts import { QueryClient } from '@tanstack/react-query' export function createQueryClient() { return new QueryClient({ defaultOptions: { queries: { // Daten gelten 2 Minuten als frisch staleTime: 2 * 60 * 1000, // Cache 10 Min nach letztem Subscriber behalten gcTime: 10 * 60 * 1000, // 1 Retry bei Netzwerkfehlern retry: 1, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000), // Refetch nur in Produktion bei Window-Focus refetchOnWindowFocus: process.env.NODE_ENV === 'production', }, mutations: { // Mutations nie automatisch wiederholen retry: 0, }, }, }) }

QueryClientProvider im App Root

Der QueryClientProvider muss so weit oben wie moglich im Komponentenbaum platziert werden. Claude Code erkennt automatisch, ob ein Next.js- oder Vite-Projekt vorliegt und passt die Wrapper-Struktur entsprechend an:

// app/providers.tsx (Next.js App Router) 'use client' import { useState } from 'react' import { QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { createQueryClient } from '@/lib/query-client' export function Providers({ children }: { children: React.ReactNode }) { // useState verhindert shared state zwischen Server Requests const [queryClient] = useState(() => createQueryClient()) return ( <QueryClientProvider client={queryClient}> {children} <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ) }

Query-Key-Factory Pattern

Das Query-Key-Factory Pattern ist einer der wichtigsten Best Practices in TanStack Query v5. Claude Code generiert es standardmaig, wenn man nach einer skalierbaren Architektur fragt — es verhindert typisierungsfehler bei der Cache-Invalidierung:

// lib/query-keys.ts — typsichere Query-Key-Factory export const userKeys = { // Basis-Key fur alle User-Queries all: ['users'] as const, // Liste mit optionalem Filter lists: () => [...userKeys.all, 'list'] as const, list: (filters: UserFilters) => [...userKeys.lists(), filters] as const, // Detail mit User-ID details: () => [...userKeys.all, 'detail'] as const, detail: (id: string) => [...userKeys.details(), id] as const, } export const postKeys = { all: ['posts'] as const, lists: () => [...postKeys.all, 'list'] as const, list: (filters: PostFilters) => [...postKeys.lists(), filters] as const, detail: (id: string) => [...postKeys.all, 'detail', id] as const, byUser: (userId: string) => [...postKeys.all, 'byUser', userId] as const, }

useQuery mit select-Transformation

Claude Code zeigt, wie der select-Parameter fur leistungsoptimierte Datentransformationen eingesetzt wird. Das Ergebnis wird nur re-berechnet, wenn sich die Quelldaten wirklich andern:

// hooks/use-user.ts import { useQuery } from '@tanstack/react-query' import { userKeys } from '@/lib/query-keys' import { fetchUser } from '@/api/users' interface User { id: string name: string email: string role: 'admin' | 'user' | 'viewer' createdAt: string } export function useUser(userId: string) { return useQuery({ queryKey: userKeys.detail(userId), queryFn: () => fetchUser(userId), // select: Daten transformieren ohne extra State select: (data) => ({ ...data, displayName: `${data.name} (${data.role})`, isAdmin: data.role === 'admin', memberSince: new Date(data.createdAt).getFullYear(), }), // Nur laden wenn userId vorhanden enabled: Boolean(userId), staleTime: 5 * 60 * 1000, }) } // Verwendung in Komponente: function UserProfile({ userId }: { userId: string }) { const { data: user, isLoading, isError } = useUser(userId) if (isLoading) return <UserSkeleton /> if (isError || !user) return <ErrorMessage /> return <div>{user.displayName} — Mitglied seit {user.memberSince}</div> }
useQuery Claude Code Prompt-Tipp

"Erstelle einen useQuery-Hook fur User-Profil-Daten mit select-Transformation, enabled-Guard und typsicherem Query-Key." — Claude Code generiert daraus direkt den vollstandigen Hook inklusive Interface-Definitionen und einer Skeleton-Komponente fur den Loading-State.

v5 API-Anderung: In React Query v4 war der zweite Parameter von useQuery ein Objekt mit Optionen. In v5 gibt es nur noch einen einzigen Parameter — das Konfigurationsobjekt. Claude Code generiert automatisch v5-kompatiblen Code und warnt bei v4-Mustern.

2. useMutation & Cache-Invalidierung

Mutations sind fur schreibende Operationen zustandig — API-POST, PUT und DELETE. React Query v5 macht die Cache-Invalidierung nach erfolgreichen Mutations einfacher und typsicherer als je zuvor.

Grundlegendes useMutation Setup

Claude Code erzeugt Mutation-Hooks, die konsistent Fehler behandeln und den Cache nach Erfolg aktualisieren. Das Muster mit separaten onSuccess/onError-Callbacks ist bewusstes Design fur klare Separation of Concerns:

// hooks/use-create-post.ts import { useMutation, useQueryClient } from '@tanstack/react-query' import { postKeys } from '@/lib/query-keys' import { createPost } from '@/api/posts' interface CreatePostInput { title: string content: string authorId: string } export function useCreatePost() { const queryClient = useQueryClient() return useMutation({ mutationFn: (input: CreatePostInput) => createPost(input), onSuccess: (newPost, variables) => { // 1. Listen invalidieren — werden automatisch neu geladen queryClient.invalidateQueries({ queryKey: postKeys.lists() }) // 2. Den neuen Post direkt in den Cache schreiben queryClient.setQueryData(postKeys.detail(newPost.id), newPost) // 3. User-spezifische Liste invalidieren queryClient.invalidateQueries({ queryKey: postKeys.byUser(variables.authorId), }) }, onError: (error, variables, context) => { console.error('Post-Erstellung fehlgeschlagen:', error) // Hier: Toast-Notification, Error-Logging etc. }, }) }

useMutation in Komponenten verwenden

Der generierte Hook integriert sich sauber in React-Komponenten. Claude Code achtet darauf, dass der Pending-State korrekt behandelt und Doppel-Submits verhindert werden:

// components/create-post-form.tsx import { useCreatePost } from '@/hooks/use-create-post' export function CreatePostForm({ authorId }: { authorId: string }) { const createPost = useCreatePost() async function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault() const formData = new FormData(event.currentTarget) await createPost.mutateAsync({ title: formData.get('title') as string, content: formData.get('content') as string, authorId, }) } return ( <form onSubmit={handleSubmit}> <input name="title" placeholder="Titel" required /> <textarea name="content" placeholder="Inhalt" required /> <button type="submit" disabled={createPost.isPending} > {createPost.isPending ? 'Wird gespeichert...' : 'Veroffentlichen'} </button> {createPost.isError && ( <p className="error">Fehler: {createPost.error.message}</p> )} </form> ) }
useMutation setQueryData vs. invalidateQueries

Claude Code erklart den Unterschied: setQueryData schreibt sofort ohne Netzwerkrequest in den Cache (gut fur neue Objekte deren vollstandige Daten bekannt sind). invalidateQueries markiert den Cache als veraltet und lost einen Refetch aus (sicherer wenn der Server die finale Form bestimmt, z.B. durch Datenbankdefaults).

Mutation State Tracking

React Query v5 bietet fur Mutations ein vollstandiges State-Set. Claude Code nutzt diese Status-Flags systematisch um optimale UX zu gewahrleisten:

Status-Flag Bedeutung Typischer Use Case
isPending Mutation lauft gerade Button deaktivieren, Spinner zeigen
isSuccess Mutation erfolgreich Success-Toast, Form zuruck setzen
isError Fehler aufgetreten Fehlermeldung anzeigen
isIdle Noch nicht gestartet Initialer Button-State
data Erfolgs-Response Neue ID verwenden fur Navigation

3. Infinite Queries & Pagination

Infinite Scrolling und cursor-basierte Pagination sind typische Use Cases fur useInfiniteQuery. Claude Code generiert den vollstandigen Stack: vom Hook bis zur IntersectionObserver-Logik fur automatisches Nachladen.

useInfiniteQuery Grundstruktur

In v5 wurde die API von useInfiniteQuery uberarbeitet. Der initialPageParam ist jetzt obligatorisch, und getNextPageParam erhalt einen strukturierteren Kontext:

// hooks/use-infinite-posts.ts import { useInfiniteQuery } from '@tanstack/react-query' import { postKeys } from '@/lib/query-keys' interface PostsPage { posts: Post[] nextCursor: string | null total: number } async function fetchPosts({ cursor, limit = 20 }: { cursor?: string limit?: number }): Promise<PostsPage> { const params = new URLSearchParams({ limit: String(limit) }) if (cursor) params.set('cursor', cursor) const res = await fetch(`/api/posts?${params}`) if (!res.ok) throw new Error('Posts konnten nicht geladen werden') return res.json() } export function useInfinitePosts(filters: PostFilters = {}) { return useInfiniteQuery({ queryKey: postKeys.list(filters), queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam }), // v5: initialPageParam ist Pflicht initialPageParam: undefined as string | undefined, // Cursor aus letzter Seite extrahieren getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, // Optionale Ruckwarts-Pagination getPreviousPageParam: (firstPage) => undefined, staleTime: 60 * 1000, }) }

IntersectionObserver fur Auto-Load

Claude Code generiert den kompletten Infinite-Scroll-Hook mit IntersectionObserver — ohne externe Bibliotheken. Das Pattern ist zuverlassig und verhindert doppeltes Laden:

// components/infinite-post-list.tsx import { useEffect, useRef } from 'react' import { useInfinitePosts } from '@/hooks/use-infinite-posts' export function InfinitePostList() { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, } = useInfinitePosts() // Sentinel-Element am Ende der Liste beobachten const sentinelRef = useRef<HTMLDivElement>(null) useEffect(() => { const sentinel = sentinelRef.current if (!sentinel) return const observer = new IntersectionObserver( (entries) => { if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) { fetchNextPage() } }, { rootMargin: '200px' } // 200px vor dem Ende starten ) observer.observe(sentinel) return () => observer.disconnect() }, [fetchNextPage, hasNextPage, isFetchingNextPage]) if (isLoading) return <PostListSkeleton /> if (isError) return <ErrorMessage /> // pages ist ein Array von Seiten — flatten fur Rendering const posts = data?.pages.flatMap((page) => page.posts) ?? [] return ( <div> {posts.map((post) => ( <PostCard key={post.id} post={post} /> ))} {/* Sentinel fur IntersectionObserver */} <div ref={sentinelRef} style={{ height: '1px' }} /> {isFetchingNextPage && <LoadingSpinner />} {!hasNextPage && posts.length > 0 && ( <p className="end-message">Alle Beitrage geladen.</p> )} </div> ) }
Performance-Tipp von Claude Code: Bei groen Listen empfiehlt Claude Code den Einsatz von @tanstack/react-virtual (TanStack Virtual) fur DOM-Virtualisierung. Die Kombination aus useInfiniteQuery und Virtual-List ist der Standard fur Listen mit tausenden von Eintrgen.

Seiten-basierte Pagination (Alternative)

Fur traditionelle Seiten-Navigation generiert Claude Code eine Variante mit page-Index statt Cursor:

// Seiten-basierte Variante mit page-Number const infiniteQuery = useInfiniteQuery({ queryKey: ['posts', filters], queryFn: ({ pageParam }) => fetchPostsPage(pageParam), initialPageParam: 1, // Nachste Seite = aktuelle + 1, wenn Daten vorhanden getNextPageParam: (lastPage, allPages) => { const hasMore = lastPage.posts.length === PAGE_SIZE return hasMore ? allPages.length + 1 : undefined }, }) // Gezieltes Laden einer bestimmten Seite infiniteQuery.fetchNextPage() infiniteQuery.fetchPreviousPage() // Alle Seiten als flache Liste const allPosts = infiniteQuery.data?.pages.flatMap(p => p.posts)

4. Optimistic Updates & Rollback

Optimistic Updates sind das Killer-Feature fur responsive UIs. Die Anderung wird sofort im UI angezeigt — und bei Fehler automatisch zuruckgerollt. Claude Code generiert das gesamte Muster inklusive Typ-sicherem Context-Objekt.

Das Optimistic Update Pattern

Das Muster besteht aus drei Phasen: Pending (sofortiges UI-Update), Error (Rollback auf alten Zustand), Settled (Cache aktualisieren). Claude Code gibt den vollstandigen Code fur alle drei Phasen aus:

// hooks/use-toggle-like.ts import { useMutation, useQueryClient } from '@tanstack/react-query' import { postKeys } from '@/lib/query-keys' interface LikeContext { previousPost: Post | undefined previousList: Post[] | undefined } export function useToggleLike(postId: string) { const queryClient = useQueryClient() return useMutation< Post, // TData: Success Response Error, // TError: Fehler-Typ void, // TVariables: Kein Input-Parameter LikeContext // TContext: Rollback-Daten >({ mutationFn: () => togglePostLike(postId), // PHASE 1: Sofortiges optimistisches Update onMutate: async () => { // Laufende Refetches abbrechen (verhindert Uberschreibung) await queryClient.cancelQueries({ queryKey: postKeys.detail(postId) }) await queryClient.cancelQueries({ queryKey: postKeys.lists() }) // Snapshot fur Rollback speichern const previousPost = queryClient.getQueryData<Post>(postKeys.detail(postId)) const previousList = queryClient.getQueryData<Post[]>(postKeys.lists()) // Detail-Cache sofort aktualisieren queryClient.setQueryData<Post>(postKeys.detail(postId), (old) => { if (!old) return old return { ...old, isLiked: !old.isLiked, likesCount: old.isLiked ? old.likesCount - 1 : old.likesCount + 1, } }) // Listen-Cache sofort aktualisieren queryClient.setQueryData<Post[]>(postKeys.lists(), (old) => old?.map((post) => post.id === postId ? { ...post, isLiked: !post.isLiked, likesCount: post.isLiked ? post.likesCount - 1 : post.likesCount + 1 } : post ) ) // Context zuruck geben fur Rollback im Error-Fall return { previousPost, previousList } }, // PHASE 2: Rollback bei Fehler onError: (err, variables, context) => { if (context?.previousPost) { queryClient.setQueryData(postKeys.detail(postId), context.previousPost) } if (context?.previousList) { queryClient.setQueryData(postKeys.lists(), context.previousList) } console.error('Like fehlgeschlagen, Rollback durchgefuhrt', err) }, // PHASE 3: Cache mit Server-Daten synchronisieren onSettled: () => { queryClient.invalidateQueries({ queryKey: postKeys.detail(postId) }) }, }) }
Cache cancelQueries ist unverzichtbar

Claude Code weist immer darauf hin: Ohne cancelQueries vor dem optimistischen Update konnte ein laufender Refetch das soeben optimistisch gesetzte Ergebnis uberschreiben. Der await ist dabei wichtig — die Cancellation muss abgeschlossen sein, bevor der Snapshot gespeichert wird.

Optimistic Update in der Praxis

// Verwendung: Like-Button mit sofortigem Feedback function LikeButton({ postId }: { postId: string }) { const { data: post } = useQuery({ queryKey: postKeys.detail(postId), queryFn: () => fetchPost(postId), }) const toggleLike = useToggleLike(postId) return ( <button onClick={() => toggleLike.mutate()} disabled={toggleLike.isPending} aria-label={post?.isLiked ? 'Like entfernen' : 'Liken'} > {post?.isLiked ? 'Geliked' : 'Liken'} ({post?.likesCount}) </button> ) }
Praxis-Erfahrung: Bei Netzwerklatenz uber 100ms ist der Unterschied mit Optimistic Updates deutlich spurbar. Claude Code empfiehlt, Optimistic Updates fur alle haufig genutzten, idempotenten Aktionen einzusetzen: Likes, Bookmarks, Toggles, Reorder-Aktionen.

5. Suspense & Error Boundaries

React Query v5 bringt natives Suspense-Support mit useSuspenseQuery. Claude Code kombiniert es mit React Error Boundaries fur deklaratives Async-Rendering ohne isLoading/isError Checks in jeder Komponente.

useSuspenseQuery statt useQuery

Der entscheidende Unterschied: useSuspenseQuery wirft Promises bei Loading und Errors direkt — dadurch entfallen die ublichen Conditional-Checks und der Typ von data ist immer definiert (kein undefined):

// hooks/use-user-suspense.ts import { useSuspenseQuery } from '@tanstack/react-query' import { userKeys } from '@/lib/query-keys' export function useUserSuspense(userId: string) { return useSuspenseQuery({ queryKey: userKeys.detail(userId), queryFn: () => fetchUser(userId), staleTime: 5 * 60 * 1000, }) } // Komponente: Kein isLoading/isError notig! function UserDetail({ userId }: { userId: string }) { // data ist immer User (niemals undefined) dank Suspense const { data: user } = useUserSuspense(userId) return ( <div> <h1>{user.name}</h1> <p>{user.email}</p> </div> ) }

Suspense Boundaries im Layout

Claude Code strukturiert die Suspense-Grenzen so, dass Skeleton-UIs gezielt fur einzelne Seitenbereiche angezeigt werden. Die Error Boundary fangt API-Fehler ohne manuelles isError-Handling:

// app/dashboard/page.tsx (Next.js App Router) import { Suspense } from 'react' import { ErrorBoundary } from 'react-error-boundary' export default function DashboardPage() { return ( <main> <h1>Dashboard</h1> {/* Unabhangige Suspense-Grenzen fur paralleles Laden */} <div className="dashboard-grid"> <ErrorBoundary fallback={<WidgetError title="Benutzer" />}> <Suspense fallback={<UserWidgetSkeleton />}> <UserWidget /> </Suspense> </ErrorBoundary> <ErrorBoundary fallback={<WidgetError title="Statistiken" />}> <Suspense fallback={<StatsSkeleton />}> <StatsWidget /> </Suspense> </ErrorBoundary> <ErrorBoundary fallback={<WidgetError title="Aktivitat" />}> <Suspense fallback={<ActivitySkeleton />}> <ActivityFeed /> </Suspense> </ErrorBoundary> </div> </main> ) }

Error Boundary mit Reset

Claude Code integriert react-error-boundary mit React Query Reset-Funktionalitat, damit Nutzer nach einem Fehler die Daten neu laden konnen:

// components/query-error-boundary.tsx import { ErrorBoundary, FallbackProps } from 'react-error-boundary' import { useQueryErrorResetBoundary } from '@tanstack/react-query' function QueryErrorFallback({ error, resetErrorBoundary }: FallbackProps) { return ( <div role="alert" className="error-fallback"> <h3>Etwas ist schief gelaufen</h3> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}> Erneut versuchen </button> </div> ) } export function QueryErrorBoundary({ children }: { children: React.ReactNode }) { const { reset } = useQueryErrorResetBoundary() return ( <ErrorBoundary onReset={reset} FallbackComponent={QueryErrorFallback} > {children} </ErrorBoundary> ) }
Suspense useSuspenseQueries fur parallele Queries

Claude Code empfiehlt useSuspenseQueries wenn mehrere Queries gleichzeitig geladen werden mussen. Alle Promises werden parallel aufgeloest — erst wenn alle fertig sind, wird die Komponente gerendert. Das verhindert die "Wasserfall"-Problematik bei sequentiellen useQuery-Aufrufen.

6. Prefetching & SSR-Integration

Prefetching und SSR-Hydration sind entscheidend fur Core Web Vitals. Claude Code generiert den kompletten Dehydrate/Hydrate-Workflow fur Next.js App Router — inkl. HydrationBoundary und Server-Component-Integration.

Prefetching in Server Components

Mit Next.js App Router konnen Queries direkt in Server Components prefetched werden. Die Daten werden dehydriert, zum Client gesendet und dort in den QueryClient hydratisiert — ohne doppelten Netzwerkrequest:

// app/posts/page.tsx — Server Component import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query' import { postKeys } from '@/lib/query-keys' import { PostList } from '@/components/post-list' import { fetchPosts } from '@/api/posts' export default async function PostsPage() { // Neuer QueryClient fur jeden Request (wichtig bei SSR!) const queryClient = new QueryClient() // Posts auf dem Server prefetchen await queryClient.prefetchQuery({ queryKey: postKeys.list({}), queryFn: () => fetchPosts({}), staleTime: 60 * 1000, }) // Mehrere Queries parallel prefetchen await Promise.all([ queryClient.prefetchQuery({ queryKey: postKeys.list({ category: 'featured' }), queryFn: () => fetchPosts({ category: 'featured' }), }), queryClient.prefetchQuery({ queryKey: ['categories'], queryFn: fetchCategories, }), ]) return ( <HydrationBoundary state={dehydrate(queryClient)}> <PostList /> </HydrationBoundary> ) }

Client Component mit hydrierten Daten

Die Client Component nutzt useQuery wie gewohnt. React Query erkennt automatisch, dass der Cache bereits befullte Daten enthalt und macht keinen erneuten Netzwerkrequest:

// components/post-list.tsx — Client Component 'use client' import { useQuery } from '@tanstack/react-query' import { postKeys } from '@/lib/query-keys' export function PostList() { // Kein Loading-State: Daten sind bereits vom Server vorhanden const { data: posts } = useQuery({ queryKey: postKeys.list({}), queryFn: () => fetchPosts({}), // staleTime verhindert sofortigen Refetch nach Hydration staleTime: 60 * 1000, }) return ( <div className="post-grid"> {posts?.map((post) => ( <PostCard key={post.id} post={post} /> ))} </div> ) }

prefetchQuery vs. prefetchInfiniteQuery

// Infinite Query auch auf dem Server prefetchen await queryClient.prefetchInfiniteQuery({ queryKey: postKeys.list({ type: 'infinite' }), queryFn: ({ pageParam }) => fetchPosts({ cursor: pageParam }), initialPageParam: undefined, // Nur die erste Seite prefetchen — Rest per Infinite Scroll pages: 1, })

Streaming mit Suspense (Next.js App Router)

Claude Code kombiniert React Query Prefetching mit Next.js Streaming fur progressive Seitenauslieferung. Kritische Daten werden synchron gerendert, weniger wichtige Inhalte werden gestreamt:

// app/dashboard/page.tsx — Streaming Setup import { Suspense } from 'react' import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query' export default async function DashboardPage() { const queryClient = new QueryClient() // Kritische Daten: await (blockiert bis bereit) await queryClient.prefetchQuery({ queryKey: ['user', 'me'], queryFn: fetchCurrentUser, }) return ( <HydrationBoundary state={dehydrate(queryClient)}> <UserHeader /> {/* Sofort sichtbar */} <Suspense fallback={<StatsSkeleton />}> {/* Gestreamt wenn bereit */} <AsyncStatsSection queryClient={queryClient} /> </Suspense> <Suspense fallback={<FeedSkeleton />}> <AsyncActivityFeed queryClient={queryClient} /> </Suspense> </HydrationBoundary> ) }
SSR Wichtig: Pro-Request QueryClient

Claude Code betont immer: In SSR-Kontexten muss new QueryClient() fur jeden einzelnen Request aufgerufen werden — niemals ein globaler Singleton auf dem Server. Andernfalls teilen sich verschiedene Nutzer denselben Cache, was zu Datenlecks fuhrt. Der Singleton-Pattern ist nur auf dem Client sicher (via useState).

Methode Zeitpunkt Netzwerkrequest Client Best fur
prefetchQuery (Server) Vor erstem Render Kein Request (Cache-Hit) Kritische Seitendaten, SEO
prefetchQuery (Client) Hover/Navigation Vorausgeladener Cache Antizipierte Navigation
useQuery ohne Prefetch Beim Mounten Immer Request Seltene/nutzerspezifische Daten
initialData Synchron Nur nach staleTime Statische/seltene Daten

Fazit: React Query v5 mit Claude Code

TanStack Query v5 ist 2026 die klarste Wahl fur Server State Management in React-Projekten. Die API ist konsistenter als je zuvor, TypeScript-Support ist erstklassig und die Kombination aus Suspense, SSR-Hydration und DevTools macht die Entwicklung signifikant produktiver.

Claude Code beschleunigt die Implementierung auf mehreren Ebenen:

Was Claude Code nicht ersetzt: Das Verstandnis fur wann welches Pattern sinnvoll ist — Optimistic Updates sind nicht fur jede Mutation geeignet, Suspense erfordert durchdachte Error-Boundaries, SSR-Prefetching macht nur Sinn fur tatsachlich SEO-kritische Inhalte. Das architektonische Urteil bleibt beim Entwickler.

Die sechs vorgestellten Muster — QueryClient-Setup, Mutations, Infinite Queries, Optimistic Updates, Suspense und SSR-Integration — decken den Groten Teil realer React-Projekte ab. Mit Claude Code als Implementierungsassistent lasst sich der initiale Aufbau dieser Architektur von Tagen auf Stunden reduzieren.

State-Management-Modul im Kurs

Im Claude Code Mastery Kurs: vollstandiges TanStack Query-Modul mit Optimistic Updates, Infinite Queries, Suspense und SSR-Integration fur Next.js. Schritt-fur-Schritt mit echten Projekten.

14 Tage kostenlos testen →

Weitere Artikel

React & State

Zustand State Management mit Claude Code 2026

Next.js

Next.js App Router mit Claude Code: Vollstandiger Guide

TypeScript

TypeScript Generics meistern mit Claude Code