100.000 Zeilen ohne Lag
Wer 10.000 React-Elemente ins DOM schreibt, baut eine Anwendung, die für Nutzer nicht bedienbar ist. Virtualisierung rendert nur die sichtbaren Zeilen — der Rest ist Mathematik. Wie man das mit TanStack Virtual, react-window und eigenen Implementierungen umsetzt.
Inhaltsverzeichnis
- 1. Das Problem: DOM-Knoten kosten Speicher und Zeit
- 2. Das Grundprinzip der Virtualisierung
- 3. TanStack Virtual: moderner Standard
- 4. Fixed-Height-Listen implementieren
- 5. Dynamic Heights: variable Zeilenhöhen
- 6. Horizontales Scrolling und 2D-Grids
- 7. Infinite Scroll mit Virtualisierung
- 8. Häufige Pitfalls und wie man sie löst
- 9. Bibliotheken im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Problem: DOM-Knoten kosten Speicher und Zeit
Ein einzelner React-Listeneintrag erzeugt im DOM mehrere Knoten, jeweils mit eigenem Layout-Kontext, Event-Listener-Infrastruktur und Rendering-Budget. Bei 1.000 Einträgen ist das in modernen Browsern noch handhabbar. Bei 10.000 Einträgen beginnt das initiale Rendering mehrere Sekunden zu dauern — selbst wenn diese Einträge alle unterhalb des Viewports liegen und für den Nutzer nicht sichtbar sind. Bei 100.000 Einträgen ist die Anwendung auf durchschnittlicher Hardware schlicht nicht bedienbar: Scrolling ruckelt, Interaktionen verzögern sich um hunderte Millisekunden, der Tab belegt Gigabytes RAM.
Das Problem ist nicht React spezifisch — es ist ein fundamentales Browser-Problem. Der Browser muss alle DOM-Knoten in seinem Layout-Tree halten, Reflows für gesamte Listen berechnen und Paint-Operationen für den gesamten sichtbaren Bereich koordinieren. Virtualisierung löst dieses Problem radikal: statt alle Elemente ins DOM zu schreiben, werden nur die Elemente gerendert, die aktuell im Viewport oder kurz davor sind. Der Schlüssel liegt darin, die Position aller Elemente mathematisch zu kennen, ohne sie zu rendern — und bei Scroll-Events dynamisch die sichtbaren Elemente neu zu bestimmen.
2. Das Grundprinzip der Virtualisierung
Eine virtuelle Liste funktioniert mit drei Bausteinen: einem äußeren Container mit fester Höhe und overflow: auto, einem inneren Container, der die Gesamthöhe aller Items simuliert (damit der Scrollbar korrekt ist), und den tatsächlich gerenderten Items, die mit position: absolute und einem berechneten transform: translateY() an die korrekte Scrollposition gesetzt werden.
Das Scrolling-Event aktualisiert den sichtbaren Bereich: React berechnet, welche Items im aktuellen Viewport-Bereich liegen, und rendert nur diese. Alle anderen Items sind nicht im DOM — sie existieren nur als Mathematik. Das bedeutet: die DOM-Tiefe bleibt konstant, egal ob die Liste 100 oder 1.000.000 Einträge hat. Der Rendering-Aufwand skaliert mit der Viewport-Größe, nicht mit der Datensatz-Größe. Das ist der fundamentale Performance-Gewinn der React-Virtualisierung.
// TanStack Virtual v3: virtual list with fixed row height
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef } from 'react';
interface Row {
id: number;
name: string;
email: string;
}
function VirtualProductList({ rows }: { rows: Row[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: rows.length, // total number of items (e.g. 100_000)
getScrollElement: () => parentRef.current,
estimateSize: () => 56, // estimated row height in px
overscan: 10, // render 10 extra rows above/below viewport
});
return (
// Outer container: fixed height, scrollable
<div ref={parentRef} style={ { height: '600px', overflow: 'auto' } }>
{/* Inner container: simulates total list height for correct scrollbar */}
<div style={ { height: `${virtualizer.getTotalSize()}px`, position: 'relative' } }>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement} // enables dynamic height measurement
style={ {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
} }
>
<ProductRow row={rows[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
3. TanStack Virtual: moderner Standard
TanStack Virtual v3 ist in 2026 der de-facto Standard für React-Virtualisierung. Die Bibliothek ist framework-agnostisch (React, Vue, Solid, Svelte), hat eine klare API und unterstützt sowohl Fixed-Height als auch Dynamic-Height-Listen nativ. Im Gegensatz zu älteren Bibliotheken wie react-window kommt TanStack Virtual ohne starre Komponenten-APIs aus — es ist ein Headless-Virtualizer, der nur die Berechnungen liefert und dem Entwickler die volle Kontrolle über das Rendering überlässt. Das macht Integration mit Tailwind CSS, komplexen Row-Layouts und Custom-Scrollbar-Implementierungen erheblich einfacher.
Die wichtigsten Parameter des useVirtualizer-Hooks: count (Gesamtanzahl der Items), getScrollElement (Referenz auf den scrollbaren Container), estimateSize (initiale Schätzung der Item-Höhe), und overscan (Anzahl der extra gerenderten Items über und unter dem Viewport). Das overscan-Attribut ist entscheidend für flüssiges Scrolling: Zu wenig Overscan führt zu sichtbarem Flicker beim schnellen Scrollen, zu viel Overscan erhöht den Rendering-Aufwand unnötig. 5–15 Items ist typischerweise der richtige Bereich.
4. Fixed-Height-Listen implementieren
Die einfachste und performanteste Form der React-Virtualisierung ist die Fixed-Height-Liste: Alle Items haben dieselbe Höhe, was die Positionsberechnung trivial macht. Der Virtualizer muss nicht messen — er berechnet alle Positionen aus der bekannten Höhe und dem Index. Das ist O(1) pro Item statt O(n) für dynamische Höhen. Für Tabellen, Dateilisten, Suchergebnisse und Produktlisten mit gleichmäßigem Layout ist Fixed-Height die erste Wahl.
Ein kritischer Punkt bei Fixed-Height-Listen: die estimateSize-Funktion muss exakt der tatsächlich gerenderten Höhe entsprechen, inklusive Padding, Border und Margin des Row-Containers. Wenn die Schätzung abweicht, entstehen Lücken oder überlappende Zeilen beim Scrollen. Am zuverlässigsten: die tatsächliche Row-Höhe einmal mit den Browser DevTools messen und als konstanten Wert übergeben. Bei Responsive-Designs, die unterschiedliche Höhen auf verschiedenen Viewport-Breiten haben, muss der Wert entsprechend dynamisch berechnet werden.
5. Dynamic Heights: variable Zeilenhöhen
Dynamic-Height-Virtualisierung ist erheblich komplexer als Fixed-Height, aber für viele reale Use Cases notwendig: Kommentarlisten mit variablem Textinhalt, Feeds mit Bildern, Chat-Nachrichten oder Tabellen mit mehrzeiligen Inhalten. TanStack Virtual löst das Problem elegant mit dem measureElement-Callback: Jedes gerenderte Item misst seine tatsächliche Höhe und gibt diesen Wert an den Virtualizer zurück. Der Virtualizer aktualisiert dann die Positionsberechnung für alle nachfolgenden Items.
Das Messen passiert mit einem ResizeObserver intern im Virtualizer und ist nicht-blockierend. Beim ersten Render werden Items mit der geschätzten Höhe positioniert — nach der Messung korrigiert der Virtualizer die Positionen. Das führt zu einem kurzen Layout-Shift beim ersten Render, der für den Nutzer kaum wahrnehmbar ist, wenn die Schätzung nah an der Realität liegt. Eine gute Strategie: eine großzügige Schätzung für den Median der erwarteten Höhen, kombiniert mit einem kleinen Overscan, um Layout-Shifts beim schnellen Scrollen zu minimieren.
// Dynamic height virtualization with measureElement
import { useVirtualizer } from '@tanstack/react-virtual';
import { useRef, useCallback } from 'react';
function DynamicHeightFeed({ posts }: { posts: Post[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: posts.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 200, // generous estimate for median post height
overscan: 5,
});
// measureElement enables dynamic height measurement via ResizeObserver
const measureRef = useCallback(
(node: HTMLDivElement | null) => {
if (node) virtualizer.measureElement(node);
},
[virtualizer]
);
return (
<div ref={parentRef} style={ { height: '100vh', overflow: 'auto' } }>
<div style={ { height: `${virtualizer.getTotalSize()}px`, position: 'relative' } }>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
data-index={virtualRow.index} // required for measureElement to work
ref={measureRef}
style={ {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
} }
>
<PostCard post={posts[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
6. Horizontales Scrolling und 2D-Grids
Horizontale React-Virtualisierung folgt demselben Prinzip wie die vertikale Variante, aber in der X-Achse. TanStack Virtual unterstützt horizontale Listen mit dem horizontal: true-Parameter. Für 2D-Grids — etwa eine Tabelle mit tausenden Zeilen und Spalten — werden zwei Virtualizer kombiniert: einer für die Zeilen und einer für die Spalten. Nur die Zellen, die sich im Schnitt des sichtbaren Zeilen- und Spaltenbereichs befinden, werden gerendert. Das ist der Schlüssel für die Darstellung von Spreadsheet-artigen Datenmengen, die weder Excel noch Google Sheets intern anders lösen.
Ein 2D-Virtualizer für ein 10.000×100-Grid rendert bei einem Standard-Viewport typischerweise nur 15–20 Zeilen und 8–12 Spalten gleichzeitig — also maximal 240 Zellen statt 1.000.000. Der initiale Render erfolgt in Millisekunden, Scrolling ist butterweich. Die Implementierung erfordert eine sorgfältige Handhabung des Scroll-Containers: Der Outer-Container muss sowohl vertikal als auch horizontal scrollbar sein, und die Zellen müssen in einem absolut positionierten 2D-Grid liegen, nicht in einem HTML-Table-Element (das würde die Positionsberechnung erheblich erschweren).
7. Infinite Scroll mit Virtualisierung
Die Kombination von Infinite Scroll und Virtualisierung ist das leistungsfähigste Pattern für große, paginierte Datensätze. Der Nutzer scrollt durch eine Liste, die scheinbar endlos weitergeht — tatsächlich werden bei Erreichen des Endes neue Daten nachgeladen und in den virtualisierten Datensatz eingefügt. Das kritische Detail: Beim Nachladen muss der Virtualizer über die neue Gesamtanzahl informiert werden, ohne die Scrollposition zu verändern. TanStack Virtual handhabt das automatisch, wenn count aktualisiert wird.
Die Trigger-Logik für das Nachladen ist ein weiteres wichtiges Pattern: Am Ende der virtuellen Items wird ein Sentinel-Element eingefügt, das mit einem IntersectionObserver beobachtet wird. Wenn der Sentinel sichtbar wird, wird der nächste Datensatz angefordert. Das ist effizienter als Scroll-Event-basierte Trigger, weil IntersectionObserver nicht auf dem Hauptthread läuft und keine teuren getBoundingClientRect-Aufrufe erfordert. Kombiniert mit React Query und der fetchNextPage-Funktion von useInfiniteQuery ergibt sich ein vollständiges, robustes Infinite-Scroll-Pattern.
8. Häufige Pitfalls und wie man sie löst
Das häufigste Problem bei der Virtual List-Implementierung: die Scroll-Container-Konfiguration. Der äußere Container muss eine feste Höhe und overflow: auto oder overflow: scroll haben. Wenn die Höhe von einem Flex-Container oder Grid-Layout kommt, muss sichergestellt werden, dass der Container tatsächlich eine berechnete Pixelhöhe erhält — nicht height: auto. Das ist die häufigste Ursache dafür, dass ein Virtualizer nicht scrollt oder alle Items auf einmal rendert.
Ein zweites häufiges Problem: Komponenten innerhalb der virtualisierten Liste, die beim Remount ihren Zustand verlieren. Da der Virtualizer Items aus dem DOM entfernt und wieder einfügt, werden alle Komponenten unmounted und neu gemountet, wenn sie in den Viewport scrollen. State in diesen Komponenten geht verloren. Die Lösung: State, der über Scroll-Events hinaus erhalten bleiben soll, muss außerhalb des virtualisierten Items gehalten werden — in einem Store, einem übergeordneten State oder einer Cache-Schicht wie React Query.
9. Bibliotheken im Vergleich
Die Wahl der Virtualisierungsbibliothek hängt von den spezifischen Anforderungen ab: Einfachheit vs. Flexibilität, Fixed vs. Dynamic Heights, Community-Support und Bundle-Größe.
| Bibliothek | Dynamic Heights | Headless | Bundle | Empfehlung |
|---|---|---|---|---|
| TanStack Virtual v3 | Ja (nativ) | Ja | ~5 kB | Neuprojekte 2026 |
| react-window | Nur mit Plugin | Nein | ~6 kB | Bestehende Projekte |
| react-virtuoso | Ja | Nein | ~25 kB | Einfache Integration |
| Eigene Implementierung | Möglich | Ja | 0 kB | Nur bei speziellen Anforderungen |
10. Zusammenfassung
React-Virtualisierung ist die einzige realistische Lösung für Listen mit mehr als einigen tausend Einträgen. Das Grundprinzip ist stabil und bibliotheksunabhängig: nur sichtbare Items rendern, Gesamthöhe mathematisch simulieren, Positionen bei Scroll-Events neu berechnen. TanStack Virtual v3 implementiert dieses Prinzip als headless Virtualizer mit voller Kontrolle über das Rendering — ideal für moderne React-Projekte mit komplexen Layouts.
Die wichtigsten Punkte: Fixed-Height-Listen sind immer einfacher und performanter als Dynamic-Height-Listen — wenn möglich, einheitliche Zeilenhöhen verwenden. Der Scroll-Container muss eine explizite Pixelhöhe haben. State in virtualisierten Items geht bei Unmount verloren — externen State für persistent data nutzen. Infinite Scroll mit IntersectionObserver-Trigger und React Query ist das robusteste Pattern für paginierte Datensätze. Und: Overscan zwischen 5 und 15 Items ist der richtige Kompromiss zwischen flüssigem Scrolling und Rendering-Overhead.
React Virtual List — Das Wichtigste auf einen Blick
Nur sichtbare Items rendern
Der Virtualizer rendert nur Items im Viewport plus Overscan. DOM-Tiefe bleibt konstant — egal ob 100 oder 100.000 Items.
Scroll-Container-Setup
Äußerer Container braucht feste Pixelhöhe und overflow: auto. Innerer Container simuliert Gesamthöhe. Items mit position: absolute und translateY positioniert.
TanStack Virtual v3
Headless, 5 kB, unterstützt Fixed- und Dynamic Heights nativ. measureElement für automatische Höhenmessung via ResizeObserver.
State-Management
Items werden bei Scroll unmounted. State in virtualisierten Komponenten geht verloren — in externem Store oder React Query halten.
Mironsoft
React-Performance-Optimierung, Virtualisierung und große Datensätze
React-Anwendung mit großen Listen optimieren?
Wir analysieren Performance-Probleme in React-Anwendungen, implementieren Virtualisierung mit TanStack Virtual und optimieren Infinite-Scroll-Pattern für große Datensätze.
Performance-Audit
React-Profiler-Analyse, Rendering-Bottlenecks und DOM-Größen-Probleme identifizieren
Implementierung
Virtual Lists, Dynamic Heights und 2D-Grids mit TanStack Virtual implementieren
Infinite Scroll
IntersectionObserver-Trigger und React-Query-Integration für robustes paginiertes Laden