React Virtual List: 100.000 Zeilen ohne Lag
AI generated
</>
{ }
React · Virtualisierung · Performance · TanStack Virtual
React Virtual List:
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.

15 Min. Lesezeit TanStack Virtual · react-window · Windowing · Dynamic Heights React 18 · React 19 · TypeScript

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

11. FAQ: React Virtual List

1Ab wann macht eine Virtual List Sinn?
Ab 500–1000 sichtbaren Items im DOM. Bei komplexen Layouts früher. Bei simplen Text-Zeilen erst ab mehreren tausend Items.
2TanStack Virtual vs. react-window?
TanStack Virtual ist headless und flexibel. react-window hat fertige Listenkomponenten, aber weniger Flexibilität. Für Neuprojekte: TanStack Virtual.
3Was ist Overscan?
Extra gerenderte Items über/unter dem Viewport. Zu wenig = Flicker beim Scrollen. 5–15 Items ist der richtige Kompromiss.
4Dynamic Heights handhaben?
measureElement in TanStack Virtual — jedes Item misst seine tatsächliche Höhe via ResizeObserver. Großzügige Schätzung für estimateSize reduziert Layout-Shift.
5State geht beim Scrollen verloren?
Ja — Items werden unmounted. Persistenten State in React Query, externem Store oder übergeordnetem State halten.
6Infinite Scroll mit Virtualisierung?
Sentinel-Element mit IntersectionObserver. Wenn sichtbar: nächste Seite laden, count aktualisieren. React Query useInfiniteQuery für vollständiges Pattern.
7Virtualizer scrollt nicht — warum?
Häufigstes Problem: kein explizites height-Pixel am Scroll-Container. height: auto reicht nicht. Container braucht berechnete Pixelhöhe und overflow: auto.
8Virtualizer mit HTML-Table kombinieren?
Nicht direkt. div-basiertes CSS-Grid-Layout verwenden — optisch identisch, kompatibel mit absoluter Positionierung des Virtualizers.
9Wie funktioniert ein 2D-Virtualizer?
Zwei useVirtualizer-Instanzen: eine für Zeilen, eine für Spalten. Nur Zellen im Schnittbereich werden gerendert. TanStack Virtual mit horizontal-Parameter für Spalten.
10Beeinflusst Virtualisierung SEO?
Ja bei CSR. Suchmaschinen sehen nur initiale DOM-Items. SEO-relevante Listen: SSR oder statische Generierung für erste n Einträge, Virtualisierung nur für scrollbare Ansicht.