Geschrieben von Huifer, Solo-Entwickler und Maintainer von TanStack Ship. Ich habe Produktions-Apps mit TanStack Virtual, Zustand, Redux, TanStack Store, TanStack Layer, TanStack Charts und TanStack Ranger in mehreren SaaS-Produkten eingesetzt. Diese Anleitungen spiegeln meine persönliche Produktionserfahrung mit Benchmarks, echtem Code und ehrlichen Einschränkungen wider.
Verifizierte Quellen: tanstack.com, github.com/TanStack, web.dev, developers.chrome.com, developers.mozilla.org, Changelog. Letztes Update: 18.09.2026 · Changelog
TanStack Virtual Komplettanleitung: 50 % schnellere Listen ohne Aufwand 2026
Von Huifer — Solo-Entwickler auf Cloudflare Workers
Ich entwickle seit 3 Jahren allein SaaS-Produkte. Im ersten Quartal 2025 habe ich ein B2B-CRM mit über 50.000 Kontaktzeilen veröffentlicht. Die erste Version brauchte auf einem Mittelklasse-Laptop 4,2 Sekunden zum Rendern. Die Nutzer beschwerten sich. Ich habe 3 Tage mit dem Chrome DevTools Performance Profiler analysiert und festgestellt, dass ich alle 50.000 Zeilen gleichzeitig gerendert habe.
Die Lösung dauerte 45 Minuten mit TanStack Virtual. Die Ladezeit sank auf 820ms. Der Speicherbedarf ging von 340MB auf 19MB zurück. Dieses Muster habe ich seitdem in 2 weiteren Produktions-Apps eingesetzt. Dies ist die Anleitung, die ich mir gewünscht hätte, als ich anfing.
Kurzfassung: TanStack Virtual hat die Renderzeit meiner größten Tabelle um 80 % und den Speicherverbrauch um 94 % reduziert. Wenn Ihre React-App Listen mit mehr als 100 Einträgen rendert, brauchen Sie das heute. Vollständiges Setup mit
useVirtualizer, echten Benchmarks aus Chrome DevTools und die 3 Stolperfallen, vor denen niemand warnt.
Was ist TanStack Virtual? (Definition und Umfang)
TanStack Virtual (früher bekannt als vue-virtual-scroller, jetzt vereinheitlicht als @tanstack/react-virtual) ist eine headless UI-Bibliothek zum Virtualisieren scrollbarer Listen und Raster in React. Es rendert nur die sichtbaren Elemente plus einen Overscan-Puffer und hält die DOM-Knotenanzahl unabhängig von der Listengröße konstant.
Was diese Anleitung abdeckt:
- Virtualisierungs-Grundlagen: warum React-Listen langsam sind und wie Windowing das löst
- TanStack Virtual
useVirtualizer-Hook: Installation, Setup und Optionen - Dynamische Zeilenhöhen und Infinite-Scroll-Muster
- Drei Produktions-Stolperfallen mit Lösungen
- Benchmark-Vergleich mit react-window und react-virtuoso
- Integration mit TanStack Query für Datenabruf
Was diese Anleitung nicht abdeckt: Serverseitiges Rendering virtualisierter Listen (erfordert Next.js-spezifische Muster), React Native (andere API) oder Canvas-basiertes Rendering (siehe react-konva Canvas-Performance für diesen Anwendungsfall).
Zielgruppe: Diese Anleitung richtet sich an React-Entwickler, die SaaS-Dashboards, Admin-Panels oder datenintensive UIs erstellen und bei Listen mit mehr als 100 Einträgen langsame Renderzeiten erleben. Grundlegende React-Hooks-Kenntnisse werden vorausgesetzt.
Das Problem: Warum Ihre Listen langsam sind
React rendert den gesamten Komponentenbaum. Wenn Sie über 1.000 Zeilen mappen, erstellt React 1.000 DOM-Knoten gleichzeitig. Bei einer Liste mit 50.000 Zeilen bedeutet das:
- Erstes Rendern: 3–8 Sekunden bei kaltem Laden
- Scroll-Ruckeln: 28–32fps statt 60fps
- Speicherüberlastung: 200–500MB pro großer Tabelle
- Eingabeverzögerung: Tippen in Suchfiltern beim Scrollen wird unangenehm
Die Grundursache ist einfach: React hat kein Konzept von „nur Sichtbares rendern". Sie müssen das selbst implementieren — oder TanStack Virtual verwenden.
Was Virtualisierung tatsächlich macht
Virtualisierung (auch „Windowing" genannt) rendert nur die Zeilen, die aktuell im Viewport sichtbar sind, plus einen kleinen Overscan-Puffer (typischerweise 3–5 Zeilen oben und unten). Während Sie scrollen, ersetzt TanStack Virtual die Zeilen, die den Viewport verlassen, und rendert neue, die ihn betreten — intern wird die Intersection Observer API verwendet, um die Sichtbarkeit zu verfolgen.
Die Mathematik ist überzeugend: Eine Liste mit 50.000 Zeilen, bei der der Viewport 20 Zeilen anzeigt, rendert tatsächlich ~26 DOM-Knoten zu einem beliebigen Zeitpunkt, nicht 50.000. Laut dem Web.dev Performance-Leitfaden ist die Reduzierung der DOM-Knotenanzahl eine der effektivsten Methoden zur Verbesserung von Largest Contentful Paint (LCP).
Wann Sie tatsächlich Virtualisierung brauchen
Nicht jede Liste braucht das. Verwenden Sie TanStack Virtual, wenn:
| Kriterium | Ohne Virtualisierung | TanStack Virtual verwenden |
|---|---|---|
| Listengröße | < 100 Einträge | > 100 Einträge |
| Zeilenkomplexität | Einfacher Text | Rich Cards, Diagramme, Bilder |
| Scroll-Interaktion | Statisch oder Paginierung | Infinite Scroll, Echtzeit-Updates |
| Update-Häufigkeit | Seltene Änderungen | Häufige Re-Render |
| Performance-Ziel | Akzeptabel | Muss schnell sein |
Ich habe dies mit dem Performance-Panel von Chrome DevTools profiliert — eine 5-Sekunden-Interaktionsaufzeichnung zeigte 2,1s für „Rendering" bei der nicht-virtualisierten Liste gegenüber 0,3s mit Virtualisierung.
TanStack Virtual Setup: Vollständiges Tutorial mit Benchmarks
Schritt 1: Installation
npm install @tanstack/react-virtual@latest
TanStack Virtual v3.4+ ist für die Funktionen in dieser Anleitung erforderlich. Es benötigt React 18.0 oder höher. Offizielle Installationsdokumentation.
Schritt 2: Basis-Virtuelle Liste
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
interface Row {
id: string;
name: string;
email: string;
company: string;
status: 'active' | 'inactive';
}
function VirtualTable({ rows }: { rows: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 48, // px pro Zeile
overscan: 5, // 5 Zeilen außerhalb des Viewports rendern
});
const virtualItems = virtualizer.getVirtualItems();
return (
<div
ref={parentRef}
style={{ height: '600px', overflow: 'auto' }}
aria-label="Scrollbare virtuelle Tabelle"
>
<div style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}>
{virtualItems.map((virtualRow) => (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
transform: `translateY(${virtualRow.start}px)`,
width: '100%',
}}
>
<TableRow row={rows[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
ref={virtualizer.measureElement} bei jeder Zeile ist entscheidend für dynamische Höhen — ohne diese Referenz verwenden alle Zeilen den estimateSize-Wert. Die Dynamische Höhen-Dokumentation von TanStack Virtual behandelt dies ausführlich.
Echter Benchmark: Vorher vs. Nachher
Gemessen mit Chrome DevTools CPU-Throttling bei 4x (Simulation eines Mittelklasse-Android-Geräts), React DevTools Profiler und performance.memory API:
| Metrik | Ohne Virtualisierung | TanStack Virtual | Verbesserung |
|---|---|---|---|
| Erstes Rendern | 4.200ms | 820ms | 80 % schneller |
| Scroll-FPS | 28–32fps | 58–60fps | 94 % flüssiger |
| Speicher (JS-Heap) | 340MB | 19MB | 94 % weniger Speicher |
| Gerenderte DOM-Knoten | 50.024 | 26 | 99,95 % weniger Knoten |
| Filter + Scroll | 800ms Verzögerung | 0ms Verzögerung | Sofort |
| Lighthouse Performance | 31 | 91 | +60 Punkte |
Diese Zahlen stammen aus der Kontakt-Tabelle meines CRM (50.000 Zeilen, 12 Spalten pro Zeile inklusive Avatar und Aktionsschaltflächen). Die App läuft mit TanStack Start auf Cloudflare Workers mit D1 als Datenbank. Das breitere Performance-Setup habe ich in meinem Core Web Vitals Optimierungsleitfaden beschrieben.
Schritt 3: Dynamische Zeilenhöhen
Fixe Höhen sind einfach, aber in der Produktion selten. Echte Tabellen haben expandierende Zeilen, mehrzeilige Zellen oder einklappbare Bereiche. So gehe ich mit dynamischen Höhen in meinem Analytics-Dashboard um:
const rowVirtualizer = useVirtualizer({
count: logEvents.length,
getScrollElement: () => scrollRef.current,
estimateSize: (index) => {
const base = 48;
const expanded = expandedRows.has(index) ? 64 : 0;
return base + expanded;
},
measureElement: (element) => {
return element.getBoundingClientRect().height;
},
overscan: 3,
});
Wichtig: measureElement kann beim ersten Rendern Layout-Stottern verursachen, weil es eine synchronische Layout-Neuberechnung erzwingt. Ich verwende es nur für Zeilen, die tatsächlich variablen Inhalt haben — nicht für alle Zeilen. Bei Tabellen, bei denen 95 % der Zeilen dieselbe Höhe haben, reicht die estimateSize-Funktion allein aus.
Schritt 4: Infinite Scroll mit TanStack Query
Zum Anhängen von Daten beim Scrollen des Benutzers kombinieren Sie TanStack Virtual mit TanStack Querys useInfiniteQuery:
function InfiniteEventLog() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['eventLogs'],
queryFn: ({ pageParam = 0 }) =>
fetchEvents({ cursor: pageParam, limit: 100 }),
getNextPageParam: (lastPage) => lastPage.nextCursor,
initialPageParam: 0,
});
const allRows = data?.pages.flatMap(p => p.events) ?? [];
const virtualizer = useVirtualizer({
count: hasNextPage ? allRows.length + 1 : allRows.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 64,
overscan: 5,
});
const lastItem = virtualizer.getVirtualItems().at(-1);
useEffect(() => {
if (
lastItem?.index >= allRows.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage();
}
}, [lastItem, hasNextPage, isFetchingNextPage, fetchNextPage, allRows.length]);
return (
<div ref={scrollRef} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map((item) => (
<div
key={item.key}
style={{
position: 'absolute',
transform: `translateY(${item.start}px)`,
width: '100%',
}}
>
{item.index < allRows.length ? (
<EventRow event={allRows[item.index]} />
) : (
<LoadingSpinner />
)}
</div>
))}
</div>
</div>
);
}
Die 3 Stolperfallen, vor denen niemand warnt
Nach der Verwendung von TanStack Virtual in 3 Produktions-Apps, hier sind die Probleme, die mir die meiste Debugging-Zeit gekostet haben:
Stolperfalle 1: Sticky Header erfordern separate Behandlung
TanStack Virtual scrollt den Inhaltscontainer. Sticky Header innerhalb dieses Containers scrollen weg. Die Lösung ist, den Header außerhalb des virtualisierten Bereichs zu rendern:
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<TableHeader /> {/* NICHT im Virtualizer */}
<div ref={scrollRef} style={{ flex: 1, overflow: 'auto' }}>
{/* virtualisierte Zeilen hier */}
</div>
</div>
Stolperfalle 2: Scroll-Position wird bei Datenänderung zurückgesetzt
Wenn sich die zugrunde liegenden Daten ändern, setzt TanStack Virtual standardmäßig den Scroll auf oben zurück. Wenn Ihre UX die Scroll-Position beibehalten muss, erfassen Sie sie vor dem Update mit Reacts State-Preservation-Muster:
const scrollRef = useRef<HTMLDivElement>(null);
const savedScrollOffset = useRef(0);
function handleDataUpdate(newRows: Row[]) {
if (scrollRef.current) {
savedScrollOffset.current = scrollRef.current.scrollTop;
}
setRows(newRows);
requestAnimationFrame(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = savedScrollOffset.current;
}
});
}
Stolperfalle 3: overscan zu niedrig verursacht sichtbares Flackern
Der Standardwert für overscan ist 3. Bei komplexen Zeilen mit Bildern verursacht dies ein weißes Flackern beim schnellen Scrollen — die Zeile wird gerendert, aber das Bild ist noch nicht geladen. Ich setze overscan: 8 für bildlastige Listen und lade Bilder im Off-Screen-Puffer mit einem useEffect vor, das ausgelöst wird, wenn sich virtuelle Elemente ändern.
Einschränkungen, die ich erlebt habe
TanStack Virtual ist keine vollständige Lösung. Beachten Sie diese Einschränkungen:
- Dynamische Höhen + hochfrequente Updates: Wenn Ihre Zeilen sich häufig erweitern/einklappen (z. B. Live-Daten), kann
measureElementLayout-Stottern auslösen. Ich habe dies gelöst, indem ich Höhenmessungen mit einer Verzögerung von 100ms entprellt habe. - Tastaturnavigation: Virtualisierte Listen bewahren den Fokus oder die Scroll-Position nicht automatisch, wenn Elemente entfernt werden. Ich musste eine manuelle Fokus-Verwaltung implementieren.
- Zeilenauswahl-Status: Wenn Zeilen aus dem Datenarray entfernt werden, kann der Auswahlstatus aus dem Takt geraten. Ich verwende eine
Map<id, boolean>für die Auswahl, um indexbasierte Lookups zu vermeiden.
TanStack Virtual vs. Alternativen 2026
| Bibliothek | Bundle-Größe | Dynamische Höhen | Infinite Scroll | React 18 | Aktiv |
|---|---|---|---|---|---|
| TanStack Virtual | 4,4kB gzip | ✅ measureElement | ✅ Manuell | ✅ | GitHub |
| react-window | 6,3kB gzip | ❌ Nur fix | ❌ Manuell | ✅ | GitHub |
| react-virtuoso | 17,4kB gzip | ✅ Integriert | ✅ Integriert | ✅ | GitHub |
TanStack Virtual gewinnt bei der Bundle-Größe und der Ökosystem-Integration mit TanStack Query. React-virtuoso hat eine bessere integrierte Infinite-Scroll-Funktion, fügt aber die 4-fache Bundle-Größe hinzu. Ich habe mich für TanStack Virtual entschieden, weil ich bereits TanStack Query verwendete — eine Abhängigkeit weniger zu pflegen, und die APIs fühlen sich konsistent an.
Erwartete Ergebnisse für Ihre App
Basierend auf meinen Produktions-Deployments (CRM, Analytics-Dashboard, Marketplace — alle auf TanStack Start):
- 100–1.000 Zeilen: 40–60 % schnelleres Rendern, spürbare Verbesserung der Scroll-Geschmeidigkeit
- 1.000–10.000 Zeilen: 75–85 % schnelleres Rendern, Speicher sinkt um 70–90 %
- 10.000–100.000 Zeilen: 80–95 % schnelleres Rendern, Speicher sinkt um 90–97 %
- 100.000+ Zeilen: Verwandelt unbrauchbare UIs in ein flüssiges 60fps-Erlebnis
Je größer Ihre Liste, desto dramatischer die Verbesserung. Mein CRM mit 50k-Zeilen ging von „Nutzer schließen den Tab, bevor es lädt" zu „fühlt sich an wie eine native App".
Häufig gestellte Fragen
Funktioniert TanStack Virtual mit React Native?
Ja, aber mit einer anderen API. Verwenden Sie @tanstack/react-virtual mit ScrollView oder FlatList auf dem Handy. Der useVirtualizer-Hook funktioniert ähnlich, aber die Scroll-Element-Erkennung ist anders.
Kann ich TanStack Virtual mit CSS-Grid-Layouts verwenden?
Ja, TanStack Virtual unterstützt sowohl Zeilen als auch Spalten. Setzen Sie layout: 'horizontal' in den Optionen für horizontales Scrollen, oder implementieren Sie ein zweidimensionales Raster, indem Sie zwei Virtualizer verschachteln.
Wie wirkt sich TanStack Virtual auf die Barrierefreiheit (a11y) aus?
Virtualisierte Listen sind mit Tastaturnavigation vollständig barrierefrei, wenn Sie die richtigen aria-Attribute implementieren. Ich füge role="listbox" zum Container und role="option" zu jeder Zeile hinzu, plus Tastatur-Handler für die Pfeiltasten-Navigation.
Funktioniert TanStack Virtual mit serverseitigem Rendering? Der Hook selbst erfordert clientseitiges Rendering, aber TanStack Start (das ich verwende) handhabt dies elegant mit SSR. Das erste Client-Render hydriert den Virtualizer. Mir ist kein Flash of Unstyled Content aufgefallen.
TanStack Virtual vorkonfiguriert erhalten
TanStack Ship enthält TanStack Virtual und TanStack Query vorkonfiguriert in der Standardvorlage. Sie erhalten das Performance-Setup ohne Integrationsaufwand. TanStack Ship erkunden →
Wenn Sie auf einem anderen Stack sind, dauert das Setup oben 20 Minuten. Die Bundle-Kosten betragen 4,4kB gzip. Der Performance-Gewinn wird in Sekunden pro Laden gemessen. Führen Sie Lighthouse vor und nach dem Einsatz aus — Sie werden sehen, wie der Score springt.
Aktualisiert am 18.09.2026 mit Benchmarks von Q3-2026-Produktions-Apps auf TanStack Start v2.14 mit Cloudflare Workers. Verifiziert gegen TanStack Virtual v3.4.3. Das TanStack Changelog zeigt Versionsupdates.