Hast du schon einmal einen Tooltip gebaut, der von der oberen linken Ecke an seine richtige Stelle springt? Oder ein Modal, das kurz in der falschen Größe aufblinkt, bevor es sich einpendelt? Dieses Bruchteilsekunden-Glitch ist ein Layout-Flackern. Es passiert, wenn React das DOM liest, eine Korrektur berechnet und den State aktualisiert, aber der Browser bereits damit begonnen hat, Pixel auf den Bildschirm zu bringen. Die übliche Lösung ist, useEffect durch useLayoutEffect zu ersetzen. Der Austausch funktioniert, aber nur, wenn man genau versteht, wann jeder Hook innerhalb der Browser-Pipeline ausgeführt wird.

Die Browser-Pipeline: Render, Commit, Paint

React aktualisiert eine Komponente in drei verschiedenen Phasen. In der Render-Phase baut – oder baut neu – React das Virtual DOM auf und berechnet den Diff. Noch keine tatsächlichen Pixeländerungen; dies ist reine Berechnung, die im Speicher stattfindet. Als Nächstes folgt die Commit-Phase, in der React diese Änderungen auf die echten DOM-Knoten anwendet. Styles werden aktualisiert, Knoten werden eingefügt oder entfernt und Texte ändern sich.

Dann übernimmt der Browser. In der Paint-Phase berechnet die Rendering-Engine des Browsers die Layout-Geometrie und zeichnet Pixel auf den Bildschirm. Diese Sequenz ist starr. Der Browser muss das Layout abschließen, bevor er den Paint-Vorgang ausführen kann, und er muss das Malen abschließen, bevor der Benutzer etwas Neues sieht. Die Lücke zwischen Commit und Paint wird in Millisekunden gemessen, aber sie ist real, und sie ist das Zeitfenster, in dem sich useEffect und useLayoutEffect unterscheiden.

Warum useEffect das Flackern verursacht

useEffect läuft asynchron und wird so geplant, dass es ausgeführt wird, nachdem der Browser den Bildschirm bereits gemalt hat. Das DOM ist aktualisiert, die Pixel sind gezeichnet, und dann greift React wieder ein, um deinen Effect auszuführen.

Stell dir vor, du renderst ein Dropdown-Menü unter einer Schaltfläche. Innerhalb von useEffect rufst du buttonRef.current.getBoundingClientRect() auf, berechnest die korrekten Top- und Left-Koordinaten und speicherst diese im State. Da useEffect nach dem Paint läuft, hat der Browser das Dropdown bereits an seiner Standardposition gezeichnet, vielleicht bei top: 0, left: 0. Erst nach diesem Paint aktualisiert dein Effect den State. React committet die korrigierten Koordinaten, und der Browser malt erneut. Der Benutzer sieht zwei Frames: die falsche Position, dann die richtige. Dieses visuelle Springen ist das Flackern, das jeder zu vermeiden versucht.

Für den Datenabruf, API-Aufrufe, Analytics-Tracking oder das Einrichten von Event-Listenern spielt diese Verzögerung keine Rolle. Dem Benutzer ist es egal, ob ein Analytics-Beacon ein paar Millisekunden nach dem Paint ausgelöst wird. Tatsächlich hält das Verschieben nicht-visueller Aufgaben auf die Zeit nach dem Paint das initiale Rendering reaktionsschnell. Aber für layoutabhängige Korrekturen ist useEffect einfach zu spät.

Wie useLayoutEffect das Painting blockiert

useLayoutEffect läuft synchron, unmittelbar nachdem React das DOM mutiert hat, aber bevor der Browser die Chance hat, das Layout zu berechnen oder Pixel zu malen. Es blockiert die Paint-Pipeline vollständig.

Wenn du dieselbe Messung des Dropdowns innerhalb von useLayoutEffect durchführst, ändert sich die Sequenz. React committet das initiale DOM-Update, führt deinen Layout-Effect aus, und dein State-Update löst ein synchrones Re-Rendering aus. React committet die korrigierten Koordinaten, und erst dann malt der Browser. Der Benutzer sieht einen einzigen Frame, der bereits korrekt ist.

Dieses blockierende Verhalten ist sowohl das Feature als auch das Risiko. Da useLayoutEffect verhindert, dass der Browser malt, bis es abgeschlossen ist, friert jede schwere Berechnung darin die Benutzeroberfläche ein. Selbst ein paar Dutzend Millisekunden blockierter Paint fühlen sich für den Benutzer wie Ruckeln (Jank) an. Deshalb sagen die React-Dokumentationen explizit, dass man mit useEffect beginnen und erst zu useLayoutEffect wechseln sollte, wenn man tatsächlich ein Flackern beobachtet, das man nicht tolerieren kann.

Wann man welchen Hook verwendet

Der Großteil deiner Logik gehört in useEffect. Nutze es für:

  • Datenabruf von einer API
  • Einrichten von Subscriptions oder Event-Listenern
  • Senden von Analytics-Events
  • Alle Side Effects, die das Layout nicht sofort lesen oder mutieren

Reserviere useLayoutEffect für Operationen, die das DOM lesen und zurückschreiben müssen, bevor der Benutzer den Frame sieht:

  • Messen von Elementdimensionen wie Breite, Höhe oder Scrollposition
  • Berechnen von Koordinaten für Tooltips, Popover oder Kontextmenüs
  • Verhindern von sichtbaren Layout-Verschiebungen (Layout Shifts), wenn die visuelle Position von der gerenderten Geometrie abhängt

Wenn du unsicher bist, wähle standardmäßig useEffect. Wechsel erst zu useLayoutEffect, wenn du visuelle Instabilität bemerkst. Allein diese Regel wird dazu führen, dass die überwältigende Mehrheit der React-Anwendungen reibungslos läuft.

Die Server-Side-Rendering-Falle

Wenn Sie Next.js, Remix oder ein Framework verwenden, das React auf dem Server rendert, erhalten Sie eine Warnung bei useLayoutEffect. Da der Server über kein DOM verfügt, hat der Hook nichts zum Messen. React warnt Sie, dass eine Browser-Umgebung erwartet wurde, diese jedoch nicht gefunden wurde. Während der Hydrierung kann dieser Mismatch auch subtile Fehler verursachen, da das serverseitig gerenderte Markup und das erste beabsichtigte Rendering auf dem Client voneinander abweichen können.

Die Standardlösung ist ein isomorpher Hook, der den richtigen Effekt basierend auf der Umgebung auswählt:

const useIsomorphicLayoutEffect =
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

Verwenden Sie diesen Wrapper in jeder Komponente, die DOM-Knoten messen muss, aber während des Server-Renderings ausgeführt werden könnte. Er unterdrückt die Warnung und hält Ihre Server-Ausgabe konsistent.

Performance und Best Practices

Da useLayoutEffect das Painting blockiert, halten Sie den Body des Hooks so leicht wie möglich. Lesen Sie den Layout-Wert, berechnen Sie die Korrektur und schreiben Sie sie zurück. Laden Sie keine Daten, parsen Sie keine großen Objekte oder führen Sie teure Algorithmen darin aus. Schwerfälliger Code an dieser Stelle wird den Main-Thread blockieren und dazu führen, dass sich Ihre Benutzeroberfläche eingefroren anfühlt.

Wenn Sie Elemente messen, verwenden Sie React Refs anstatt document.getElementById. Refs sind an Ihre Komponenteninstanz gebunden, überstehen Re-Renders ohne Query-Tricks und funktionieren zuverlässig mit Portals oder Conditional Rendering. Globale ID-Lookups verletzen die Kapselung der Komponente und können genau in dem Moment null zurückgeben, in dem Sie sie benötigen.

useEffect ist der richtige Standard für fast jeden Side Effect. Es ermöglicht dem Browser, das Painting ohne Unterbrechung durchzuführen, und verarbeitet Daten, Events und externe Synchronisierung sauber. useLayoutEffect ist ein spezialisiertes Werkzeug für ein spezifisches Problem: das Lesen des Layouts und das Zurückschreiben vor dem Painting. Beherrschen Sie den zeitlichen Unterschied zwischen ihnen, und Sie werden aufhören, Flackern zu jagen, sondern es stattdessen verhindern.

Das wichtigste Fazit: Beginnen Sie mit useEffect für alles. In dem Moment, in dem Sie sehen, dass ein Tooltip oder Modal an die falsche Stelle blinkt, bevor es sich selbst korrigiert, ist das Ihr Signal. Wechseln Sie zu useLayoutEffect, messen Sie das DOM, passen Sie Ihr Layout an und lassen Sie den Browser einmal — und zwar korrekt — das Painting durchführen.