React-Performance 2026: Warum weniger Optimierung mehr Tempo bringt

Schnellere React-Apps: Die Autobahn-Methode gegen lahme Renders
Abstract
- #React
- #React Performance
- #React Compiler
- #useTransition
- #useDeferredValue
- #Code-Splitting
- #Lazy Loading
React ohne Stau: So bringen Sie Ihre App auf Touren
Stellen Sie sich Ihre React-Anwendung einen Moment lang als Autobahn vor. Die Komponenten sind die Autos, jedes Rendern ist eine Fahrt. Das Ziel ist dabei nicht, möglichst wenige Autos auf die Straße zu lassen, das Ziel ist ein Verkehr, der ungehindert fließt. Genau dieses Bild hilft Ihnen, moderne React-Performance zu verstehen. Und es räumt gleich mit einem hartnäckigen Missverständnis auf: Schneller wird eine Autobahn nicht durch mehr Mautstationen, sondern durch das Beseitigen von Engpässen.
Warum die meisten Performance-Tipps von gestern sind
Wenn Sie in den letzten Jahren einen Artikel über React-Performance gelesen haben, lautete der Rat vermutlich: Packen Sie alles in useMemo und useCallback, setzen Sie React.memo ein und vermeiden Sie "unnötige Re-Renders". Dieser Ratschlag war schon 2023 nicht besonders hilfreich. Im Jahr 2026, in dem der React Compiler die Memoisierung automatisch übernimmt, ist er sogar kontraproduktiv geworden.
Die wirklichen Bremsklötze moderner Anwendungen liegen ganz woanders: Ein schlecht platzierter State lässt ganze Komponentenbäume neu rendern. Aufwändige Aktualisierungen blockieren den Hauptthread, statt nebenläufige Funktionen zu nutzen. Alles wird auf einmal geladen, statt gezielt aufgeteilt und aufgeschoben. Und verkettete useEffect-Aufrufe lösen kaskadenartige Re-Renders aus. Keines dieser Probleme lösen Sie durch ein weiteres useMemo. Für alle gibt es jedoch saubere, moderne Antworten. Bringen wir also Ihre Autobahn wieder in Fahrt.
Das Gaffer-Problem: Wenn ein Bremsen die ganze Strecke lahmlegt
Auf einer echten Autobahn bremst ein Fahrer plötzlich ab. Der Hintermann bremst ebenfalls, der Nächste noch stärker und innerhalb von Sekunden steht der Verkehr kilometerlang. Niemand weiß mehr, warum. Das ursprüngliche Auto ist längst weitergefahren. Genau so kaskadieren Re-Renders in React.
Was bei einem Re-Render wirklich passiert
Ändert sich der State einer Komponente, rendert React nicht nur diese Komponente neu, sondern auch sämtliche Kindkomponenten in ihrem Teilbaum. Liegt der State ganz oben im Baum, rendert alles darunter mit, selbst die Bausteine, die diesen State gar nicht verwenden, und sogar die, die vollkommen statische Inhalte anzeigen. Ein State, der etwa eine Suchanfrage in der obersten Komponente verwaltet, sorgt bei jedem Tastendruck dafür, dass Header, Seitenleiste, Produktliste und Footer neu gerendert werden. Keiner dieser Bereiche interessiert sich für die Suchanfrage, sie alle sind bloß Gaffer.
Die Lösung: State dort platzieren, wo er gebraucht wird
Die wirksamste Gegenmaßnahme ist zugleich die einfachste: Verschieben Sie den State an die Stelle, an der er tatsächlich verwendet wird. Kapseln Sie z.B. das Suchfeld samt Ergebnissen in eine eigene Komponente, sodass der State dort lebt.
function SearchSection() {
const [searchQuery, setSearchQuery] = useState('');
return (
<div>
<SearchBar query={searchQuery} onChange={setSearchQuery} />
<SearchResults query={searchQuery} />
</div>
);
}
Tippt der Nutzer nun, rendern nur noch SearchSection und ihre Kinder neu. Header, Seitenleiste, Produktliste und Footer bleiben unberührt und das ganz ohne Memoisierung. Diese sogenannte State-Colocation ist für die meisten React-Anwendungen die wirkungsvollste Optimierung überhaupt. Nicht das Memoisieren, nicht das Lazy Loading, sondern schlicht der richtige Ort für den State.
Das Children-Muster als eleganter Umweg
Manchmal lässt sich der State nicht nach unten verschieben, weil die besitzende Komponente auch noch Kinder rendern muss, etwa ein Scroll-Tracker, der bei jedem Scroll-Pixel aktualisiert. Hier hilft das Children-Muster: Sie reichen die schweren Inhalte als children von außen hinein.
function ScrollTracker({ children }) {
const [scrollY, setScrollY] = useState(0);
// ... Scroll-Listener ...
return (
<div>
<ScrollIndicator position={scrollY} />
{children}
</div>
);
}
Da die children von der Elternkomponente erzeugt werden und nicht vom ScrollTracker, bleibt ihre Referenz unverändert. React überspringt sie beim erneuten Rendern, wieder ohne eine einzige Zeile Memoisierung, allein durch eine strukturelle Änderung.
Schluss mit dem Memoisieren um jeden Preis
Jedes useMemo und jedes useCallback in Ihrem Code ist wie eine Mautstation. Es kostet Speicher für den zwischengespeicherten Wert, es kostet bei jedem Rendern Rechenzeit für den Vergleich der Abhängigkeiten, und es kostet gedankliche Energie bei jedem, der den Code später liest. Bei billigen Operationen, etwa dem Zusammensetzen zweier Namen zu einem vollständigen Namen, ist diese Maut teurer als die Rechenarbeit, die sie angeblich einspart. Rechnen Sie solche Kleinigkeiten einfach direkt aus.
Der React Compiler ändert die Spielregeln
Der mit React 19 ausgelieferte React Compiler übernimmt die Memoisierung von Komponenten, Hooks und Werten automatisch auf Compiler-Ebene. Er analysiert Ihren Code beim Build und fügt Zwischenspeicherung genau dort ein, wo sie wirklich hilft. Das bedeutet: Manuelles useMemo und useCallback sind kaum noch nötig, und viele React.memo-Umhüllungen werden überflüssig. Ihr Code wird dadurch nicht nur schlanker, sondern auch schneller. Schreiben Sie ihn einfach lesbar, die Optimierung erledigt der Compiler.
Wann Memoisierung trotzdem sinnvoll bleibt
Es gibt nach wie vor berechtigte Ausnahmen: bei wirklich teuren Berechnungen im Millisekundenbereich, etwa dem Sortieren tausender Einträge oder komplexer Mathematik; bei Drittbibliotheken, die auf stabile Referenzen angewiesen sind, wie manche Diagramm- oder Formularbibliotheken; und bei useEffect-Abhängigkeiten, bei denen eine instabile Referenz eine Endlosschleife auslösen würde. Doch das sind Sonderfälle. Beginnen Sie ohne Memoisierung, messen Sie und fügen Sie sie nur dort hinzu, wo Sie ein echtes Problem feststellen.
Die Überholspur: nebenläufige Features nutzen
React bietet eine eigene Überholspur für aufwändige Aktualisierungen. Sie ist eines der am meisten unterschätzten Werkzeuge überhaupt.
useTransition und startTransition
Mit useTransition teilen Sie React mit: "Diese Aktualisierung ist nicht dringend, sie darf die Nutzereingabe nicht blockieren." Ohne diesen Hinweis blockiert etwa das Filtern einer großen Datenmenge den Hauptthread bei jedem Tastendruck, das Eingabefeld ruckelt. Verpacken Sie die teure Aktualisierung dagegen in startTransition, aktualisiert sich das Eingabefeld sofort, während die Ergebnisse im Hintergrund nachgereicht werden.
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // dringend: sofort
startTransition(() => {
setResults(filterItems(value)); // aufgeschoben: im Hintergrund
});
};
Tippt der Nutzer schneller, als die Ergebnisse gerendert werden können, verwirft React die veraltete Berechnung und beginnt neu, kein verschwendeter Aufwand. Der Wert isPending liefert Ihnen dabei gratis einen Ladezustand für einen Spinner.
useDeferredValue: das Tempolimit-Schild
useDeferredValue funktioniert ähnlich, jedoch für Werte, die Sie nicht selbst steuern. Kommt ein Wert als Prop von der Elternkomponente oder aus einer externen Quelle, können Sie die State-Aktualisierung nicht in startTransition einpacken. Stattdessen verzögern Sie das Rendern. React arbeitet dann noch mit dem alten Wert, während der neue im Hintergrund verarbeitet wird; die Liste kann sich dezent abdunkeln, um die Aktualisierung zu signalisieren. Die Faustregel ist denkbar einfach: useTransition verwenden Sie, wenn Sie die Aktualisierung selbst auslösen; useDeferredValue, wenn Sie den Wert von außen erhalten.
Die Auffahrt: Code-Splitting und Lazy Loading
Sie würden nicht jede Autobahnabfahrt gleichzeitig bauen, wenn die meisten Fahrer ohnehin nur eine benötigen. Laden Sie also nicht die gesamte Anwendung, wenn der Nutzer nur die aktuelle Seite braucht. Mit lazy und Suspense laden Sie Routen und schwere Bereiche erst bei Bedarf.
const Dashboard = lazy(() => import('./pages/Dashboard'));
// erst geladen, wenn der Nutzer dorthin navigiert
Wenn Sie Next.js einsetzen, was 2026 wahrscheinlich der Fall ist –, geschieht das routenbasierte Code-Splitting automatisch: Jede Seite im app/-Verzeichnis wird zu einem eigenen Chunk. Auf Komponentenebene bleibt es Handarbeit. Achten Sie darauf, nicht zu kleinteilig aufzuteilen, denn jede lazy()-Grenze bringt Zusatzaufwand mit sich. Teilen Sie Routen und schwere Funktionsbereiche auf, nicht einzelne Schaltflächen.
Die Verkehrskamera: erst messen, dann optimieren
All die genannten Techniken sind wertlos, solange Sie nur raten, wo der Engpass liegt. Die goldene Regel lautet daher: niemals optimieren, ohne vorher zu messen.
Der Profiler in den React DevTools
Öffnen Sie in den React DevTools den Profiler, starten Sie die Aufzeichnung, bedienen Sie den langsamen Teil Ihrer Anwendung und stoppen Sie wieder. Im Flame-Chart verraten die breitesten Balken die langsamsten Komponenten. Ein Klick zeigt Ihnen, warum eine Komponente neu gerendert hat und wie lange das gedauert hat. Alles unter einer Millisekunde ist nicht Ihr Problem, suchen Sie nach Komponenten mit 16 Millisekunden und mehr, denn das ist bei 60 Bildern pro Sekunde bereits ein volles Frame-Budget.
Core Web Vitals als Kompass
Für den Nutzer zählen vor allem drei Kennzahlen. Der LCP misst, wie schnell der Hauptinhalt erscheint (Ziel: unter 2,5 Sekunden). Der INP misst, wie schnell die Oberfläche auf Eingaben reagiert (Ziel: unter 200 Millisekunden), hier machen die nebenläufigen Features den größten Unterschied. Der CLS misst, wie stark der Inhalt herumspringt (Ziel: unter 0,1). Ist Ihr INP zu hoch, greifen Sie zu den Concurrent-Features; ist der LCP schlecht, sollten Sie das Laden aufteilen und optimieren.
Übersehene Gefahrenstellen
Zum Schluss noch fünf Fehlerquellen, die in der Praxis überraschend häufig auftreten, auch bei erfahrenen Entwicklern.
Erstens: Definieren Sie niemals Komponenten innerhalb anderer Komponenten. Bei jedem Rendern der Elternkomponente entsteht eine völlig neue Funktion, wodurch die Kindkomponente samt State komplett neu eingehängt statt neu gerendert wird.
Zweitens: Verwenden Sie stabile, eindeutige Schlüssel in Listen, niemals den Array-Index bei umsortierbaren Listen und niemals Math.random(), sonst hängt React die falschen DOM-Knoten aus.
Drittens: Platzieren Sie Context-Provider auf der richtigen Ebene und trennen Sie den Wert vom Setter, damit nicht jede Wertänderung die gesamte Anwendung neu rendert.
Viertens: Vermeiden Sie Ketten aus useEffect-Aufrufen, bei denen ein Effekt den nächsten auslöst; bündeln Sie zusammengehörige Daten in einem einzigen Effekt oder überlassen Sie das Laden einer spezialisierten Bibliothek.
Und fünftens: Verlagern Sie statische Objekt-, Array- und Stil-Literale aus dem JSX heraus vor die Komponente, damit nicht bei jedem Rendern eine neue Referenz entsteht und die Memoisierung untergraben wird.
Fazit
React-Performance im Jahr 2026 bedeutet nicht, immer neue Optimierungs-Hooks in den Code zu streuen, sondern überflüssige Arbeit zu entfernen. Verschieben Sie den State nach unten, teilen Sie Contexts auf, nutzen Sie nebenläufige Features für schwere Aktualisierungen, laden Sie nur, was die Nutzer gerade brauchen, und messen Sie stets, bevor Sie optimieren.
Die Memoisierung, die Sie früher mühsam von Hand erledigt haben, übernimmt inzwischen der React Compiler. Eine Autobahn wird eben nicht durch zusätzliche Mautstationen schneller, sondern durch das Beseitigen von Engpässen, das Anlegen von Überholspuren und Abfahrten genau dort, wo tatsächlich jemand herunterfährt. Ihre React-Anwendung funktioniert nach genau demselben Prinzip.
Häufig gestellte Fragen
Brauche ich useMemo und useCallback mit dem React Compiler noch?
In den allermeisten Fällen nicht mehr. Der React Compiler analysiert Ihren Code beim Build und fügt die Memoisierung automatisch dort ein, wo sie einen Nutzen bringt. Schreiben Sie Ihren Code zunächst schlicht und lesbar. Greifen Sie nur dann manuell ein, wenn ein Profiling eine echte, teure Berechnung oder eine Drittbibliothek zeigt, die auf stabile Referenzen angewiesen ist.
Wann sollte ich useTransition und wann useDeferredValue verwenden?
Die Entscheidung hängt davon ab, wer die Aktualisierung auslöst. Verwenden Sie useTransition, wenn Sie die State-Aktualisierung selbst anstoßen und sie als nicht dringend kennzeichnen möchten. Verwenden Sie useDeferredValue, wenn der betreffende Wert von außen kommt, etwa als Prop, aus einem Context oder aus URL-Parametern und Sie das Rendern verzögern wollen, ohne die Quelle des Werts zu verändern.
Wie finde ich heraus, welche Komponente meine Anwendung wirklich ausbremst?
Nutzen Sie den Profiler der React DevTools und zeichnen Sie die langsame Interaktion auf. Im Flame-Chart zeigen die breitesten Balken die langsamsten Komponenten, und ein Klick verrät Ihnen den Grund für das erneute Rendern. Komponenten mit einer Renderzeit von 16 Millisekunden und mehr sind Ihre eigentlichen Kandidaten. Ergänzend hilft der Performance-Tab von Chrome, um sogenannte Long Tasks über 50 Millisekunden aufzuspüren, die Nutzereingaben blockieren.
- Technologien
- Programmiersprachen
- Tools