27 useInsertionEffect – CSS vor dem Layout-Prozess einfügen

React hat drei Effect-Hooks: useEffect, useLayoutEffect und useInsertionEffect. Die ersten beiden kennen wir. Sie unterscheiden sich im Timing – useEffect läuft nach dem Paint, useLayoutEffect davor. useInsertionEffect ist der dritte, der noch früher läuft. So früh, dass 99% aller React-Entwickler ihn nie brauchen werden.

Aber die 1%, die ihn brauchen, brauchen ihn wirklich. CSS-in-JS-Libraries – styled-components, Emotion, Stitches – leben von diesem Hook. Ohne ihn würden diese Libraries visuell flackern, Performance-Probleme verursachen oder das Layout mehrfach berechnen lassen. useInsertionEffect löst ein sehr spezifisches Problem: Wie füge ich CSS ein, bevor der Browser überhaupt beginnt, das Layout zu berechnen?

Dieses Kapitel erklärt, warum dieser Hook existiert, wann er gebraucht wird, und warum Sie ihn wahrscheinlich nie direkt verwenden sollten – aber trotzdem indirekt von ihm profitieren, ohne es zu merken.

27.1 Das Timing-Problem: Drei Effects, drei Zeitpunkte

Um useInsertionEffect zu verstehen, müssen wir das Rendering-Timing verstehen. React rendert eine Komponente, aktualisiert das DOM, dann übernimmt der Browser. Aber dieser Prozess ist nicht monolithisch – er besteht aus klar definierten Phasen.

Die drei Effect-Hooks unterscheiden sich nur im Timing:

useInsertionEffect läuft nach DOM-Mutation, aber vor der Browser-Layout-Berechnung. Das DOM ist aktualisiert, aber der Browser hat noch nicht begonnen, Positionen, Größen oder Stile zu berechnen.

useLayoutEffect läuft nach DOM-Mutation und nach Layout-Berechnung, aber vor dem Paint. Der Browser weiß, wo alles ist und wie groß es ist, hat aber noch nichts auf den Bildschirm gezeichnet.

useEffect läuft nach allem anderen, asynchron und nicht-blockierend. Der Browser hat alles gerendert, der Nutzer sieht die UI.

Hook Läuft wann? DOM-Zugriff Layout berechnet? Paint erfolgt? Blockiert UI?
useInsertionEffect Nach DOM-Update Ja Nein Nein Ja
useLayoutEffect Nach DOM-Update + Layout Ja Ja Nein Ja
useEffect Nach Paint Ja Ja Ja Nein

Das Timing ist präzise und unveränderlich. React garantiert diese Reihenfolge.

27.2 Das CSS-in-JS-Problem

Stellen wir uns vor, wir verwenden styled-components:

const Button = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  padding: 10px 20px;
  border-radius: 4px;
`;

function App() {
  return <Button primary>Click me</Button>;
}

Was passiert hier? styled.button ist keine echte DOM-Komponente, sondern eine Wrapper-Funktion. Wenn Button rendert, generiert styled-components zur Laufzeit CSS-Regeln basierend auf den Props. Diese CSS-Regeln müssen dem DOM hinzugefügt werden – typischerweise als <style>-Tag im <head>.

Aber wann sollen sie hinzugefügt werden?

Option 1: useEffect

useEffect(() => {
  const style = document.createElement('style');
  style.textContent = `.button-abc { background: blue; }`;
  document.head.appendChild(style);
}, []);

Problem: useEffect läuft nach dem Paint. Der Browser zeichnet den Button zuerst ohne Styles, dann mit Styles. Der Nutzer sieht einen Flash of Unstyled Content (FOUC) – der Button erscheint kurz ungestylt, dann springt er zur richtigen Darstellung.

Option 2: useLayoutEffect

useLayoutEffect(() => {
  const style = document.createElement('style');
  style.textContent = `.button-abc { background: blue; }`;
  document.head.appendChild(style);
}, []);

Besser, aber nicht perfekt. useLayoutEffect läuft vor dem Paint, aber nach der Layout-Berechnung. Der Browser berechnet das Layout ohne die Styles, fügt sie dann ein, und muss das Layout erneut berechnen. Doppelte Arbeit.

Bei einem einzelnen Button ist das vernachlässigbar. Bei einer komplexen App mit Hunderten von styled-components, die bei jedem Rendering neue Styles generieren, wird es zum Performance-Problem. Jedes Re-Rendering könnte doppelte Layout-Berechnungen auslösen – Layout Thrashing.

Option 3: useInsertionEffect

useInsertionEffect(() => {
  const style = document.createElement('style');
  style.textContent = `.button-abc { background: blue; }`;
  document.head.appendChild(style);
}, []);

Perfekt. useInsertionEffect läuft nach der DOM-Mutation, aber vor der ersten Layout-Berechnung. Die Styles sind im DOM, bevor der Browser überhaupt beginnt zu rechnen. Eine Layout-Berechnung, kein Thrashing, kein FOUC.

27.3 Die API: Identisch zu useEffect

Die API ist vertraut:

useInsertionEffect(() => {
  // Setup-Code
  
  return () => {
    // Cleanup-Code
  };
}, [dependencies]);

Exakt wie useEffect und useLayoutEffect. Der Unterschied liegt nicht in der Syntax, sondern im Timing.

Ein minimales Beispiel einer CSS-Injection:

function StyledComponent({ className, children }: Props) {
  const styleId = useId();
  
  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.setAttribute('data-styled', styleId);
    style.textContent = `.${className} { color: red; font-weight: bold; }`;
    document.head.appendChild(style);
    
    return () => {
      style.remove();
    };
  }, [styleId, className]);
  
  return <div className={className}>{children}</div>;
}

Beim Mount fügt useInsertionEffect ein <style>-Tag ein. Beim Unmount entfernt die Cleanup-Funktion es. Das alles passiert vor der ersten Layout-Berechnung.

27.4 Wie CSS-in-JS-Libraries es verwenden

styled-components, Emotion und ähnliche Libraries verwenden useInsertionEffect intern. Als Nutzer dieser Libraries sehen Sie den Hook nie direkt. Aber er läuft bei jedem Rendering Ihrer gestylten Komponenten.

Ein vereinfachtes Modell, wie styled-components arbeiten könnte:

function createStyledComponent(tag: string, styles: string) {
  return function StyledComponent(props: any) {
    const className = useMemo(() => generateClassName(styles, props), [props]);
    const css = useMemo(() => generateCSS(className, styles, props), [className, props]);
    
    useInsertionEffect(() => {
      injectCSS(css);
      
      return () => {
        removeCSS(css);
      };
    }, [css]);
    
    return React.createElement(tag, { ...props, className });
  };
}

// Verwendung
const Button = createStyledComponent('button', `
  background: blue;
  padding: 10px;
`);

Bei jedem Rendering: 1. Props ändern sich 2. Neue CSS-Regeln werden generiert (useMemo) 3. useInsertionEffect injiziert die neuen Regeln 4. React rendert das <button> mit der korrekten className 5. Browser berechnet Layout mit den Styles

Ohne useInsertionEffect (vor React 18) mussten Libraries useLayoutEffect verwenden, mit den beschriebenen Performance-Implikationen. Oder sie mussten komplexe Workarounds implementieren, um Styles serverseitig zu extrahieren und clientseitig zu hydratisieren.

27.5 Wann NICHT verwenden

useInsertionEffect ist ausschließlich für CSS-Injection gedacht. React’s Dokumentation warnt explizit:

This is intended for CSS-in-JS libraries. Unless you’re building a CSS-in-JS library and need a place to inject styles, you probably want useEffect or useLayoutEffect instead.

Verwenden Sie useInsertionEffect NICHT für:

Alle diese Szenarien gehören in useEffect oder useLayoutEffect.

// ❌ FALSCH: DOM-Messung in useInsertionEffect
useInsertionEffect(() => {
  const height = ref.current?.getBoundingClientRect().height;
  setHeight(height);  // Layout noch nicht berechnet!
}, []);

// ✓ RICHTIG: useLayoutEffect für DOM-Messungen
useLayoutEffect(() => {
  const height = ref.current?.getBoundingClientRect().height;
  setHeight(height);
}, []);
// ❌ FALSCH: API-Call in useInsertionEffect
useInsertionEffect(() => {
  fetch('/api/data').then(setData);
}, []);

// ✓ RICHTIG: useEffect für Side Effects
useEffect(() => {
  fetch('/api/data').then(setData);
}, []);

Die Versuchung mag groß sein, useInsertionEffect als “super-frühen Effect” zu verwenden, aber das führt zu Bugs. Das DOM ist möglicherweise noch nicht vollständig konsistent, Layout-Informationen sind nicht verfügbar, und State-Updates können unvorhersehbar sein.

27.6 Server-Side Rendering

Wie alle Effect-Hooks läuft useInsertionEffect nur im Browser, nicht auf dem Server.

function StyledComponent() {
  useInsertionEffect(() => {
    console.log('Läuft nur im Browser');
  }, []);
  
  return <div>Content</div>;
}

Beim SSR wird der Effect-Code komplett ignoriert. Das ist korrekt – CSS-Injection ist eine Browser-Operation. Für SSR haben CSS-in-JS-Libraries separate Mechanismen:

// Server-seitiger Code (vereinfacht)
function renderToString(Component: ReactNode) {
  const { html, css } = extractCriticalCSS(Component);
  
  return `
    <style>${css}</style>
    <div id="root">${html}</div>
  `;
}

// Client-seitiger Code
function hydrate() {
  // Styles sind bereits im <head>, keine Injection nötig
  ReactDOM.hydrate(<App />, document.getElementById('root'));
}

CSS-in-JS-Libraries extrahieren die kritischen Styles serverseitig, fügen sie dem HTML hinzu, und hydratisieren clientseitig ohne erneute Injection. useInsertionEffect kommt nur bei clientseitigen Updates ins Spiel.

27.7 Performance-Implikationen

useInsertionEffect ist synchron und blockierend. Er läuft, bevor der Browser das Layout berechnet, aber das heißt auch: Er blockiert die Layout-Berechnung, bis er abgeschlossen ist.

Deshalb muss der Code in useInsertionEffect extrem schnell sein. Idealerweise nur DOM-Operationen – createElement, appendChild, setAttribute. Keine Loops, keine komplexen Berechnungen, keine async Operations.

// ✓ Gut: Schnelle DOM-Operation
useInsertionEffect(() => {
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);
}, [css]);

// ❌ Schlecht: Langsame Berechnung
useInsertionEffect(() => {
  const css = items.map(item => 
    generateComplexCSS(item)  // Teure Operation!
  ).join('\n');
  
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);
}, [items]);

// ✓ Besser: Berechnung in useMemo, Injection in useInsertionEffect
const css = useMemo(() => 
  items.map(generateComplexCSS).join('\n'),
  [items]
);

useInsertionEffect(() => {
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);
}, [css]);

Die Berechnung der Styles sollte in useMemo erfolgen (oder sogar außerhalb der Komponente, wenn möglich). useInsertionEffect sollte nur die Injection durchführen.

27.8 Ein realistisches Beispiel: Minimale CSS-in-JS-Implementierung

// Einfachste CSS-in-JS-Implementierung
const styleCache = new Map<string, HTMLStyleElement>();

function css(template: TemplateStringsArray, ...values: any[]): string {
  // Generiere CSS-String
  const cssString = template.reduce((acc, str, i) => {
    return acc + str + (values[i] ?? '');
  }, '');
  
  // Generiere eindeutige Klasse
  const hash = hashString(cssString);
  const className = `css-${hash}`;
  
  return JSON.stringify({ className, cssString });
}

function useStyles(styleData: string) {
  const { className, cssString } = JSON.parse(styleData);
  
  useInsertionEffect(() => {
    // Prüfe, ob Style bereits injiziert
    if (styleCache.has(className)) {
      return;
    }
    
    // Erstelle und injiziere Style
    const style = document.createElement('style');
    style.setAttribute('data-css', className);
    style.textContent = `.${className} { ${cssString} }`;
    document.head.appendChild(style);
    
    styleCache.set(className, style);
    
    return () => {
      // Cleanup bei Unmount
      style.remove();
      styleCache.delete(className);
    };
  }, [className, cssString]);
  
  return className;
}

// Verwendung
function Button({ primary }: { primary?: boolean }) {
  const className = useStyles(css`
    background: ${primary ? 'blue' : 'gray'};
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    
    &:hover {
      opacity: 0.8;
    }
  `);
  
  return <button className={className}>Click me</button>;
}

Diese minimale Implementierung zeigt das Kernprinzip: 1. CSS wird als String generiert 2. Ein eindeutiger Klassenname wird erstellt 3. useInsertionEffect injiziert den Style 4. Die Komponente verwendet den Klassennamen

Produktions-Libraries wie styled-components sind natürlich deutlich komplexer – sie handhaben Theming, SSR, Vendor-Prefixing, Critical CSS Extraction, und mehr. Aber das Grundprinzip bleibt gleich.

27.9 Debugging und DevTools

React DevTools zeigen useInsertionEffect wie andere Hooks an. Bei Komponenten mit vielen gestylten Children kann die Hook-Liste lang werden:

Button
  └─ useId
  └─ useInsertionEffect
  └─ useMemo
  └─ ...

Wenn Sie Performance-Probleme vermuten, prüfen Sie: 1. Wird useInsertionEffect zu oft aufgerufen? 2. Sind die Style-Strings stabil (via useMemo)? 3. Werden alte Styles korrekt entfernt (Cleanup-Funktion)?

Browser DevTools zeigen die injizierten <style>-Tags:

<head>
  <style data-styled="active">
    .css-abc123 { background: blue; }
  </style>
  <style data-styled="active">
    .css-def456 { color: red; }
  </style>
  <!-- Hunderte mehr bei großen Apps -->
</head>

Zu viele <style>-Tags können die Performance beeinträchtigen. Moderne CSS-in-JS-Libraries aggregieren Styles oder verwenden CSS Variables, um die Tag-Anzahl zu reduzieren.

27.10 Die Zukunft: CSS Modules und andere Alternativen

useInsertionEffect ist eine Lösung für ein spezifisches Problem: Runtime CSS-Generation. Aber es gibt Alternativen, die ohne Runtime-Overhead auskommen:

CSS Modules – Styles zur Build-Zeit generieren, keine Runtime-Injection.

Static CSS-in-JS (Linaria, vanilla-extract) – Schreibe CSS-in-JS-Syntax, extrahiere zur Build-Zeit zu statischem CSS.

Tailwind CSS – Utility-Classes, kein Runtime-Overhead.

CSS-in-TS (Panda CSS, StyleX) – Typsichere Styles, zur Build-Zeit extrahiert.

Diese Ansätze eliminieren die Notwendigkeit für useInsertionEffect komplett, indem sie die Style-Generation aus der Runtime in die Build-Zeit verschieben. Das ist oft schneller und einfacher.

Aber für Libraries, die echte Runtime-CSS-Generation benötigen – weil sie hochdynamische Theming unterstützen oder vollständig zur Laufzeit komponierbare Styles bieten – bleibt useInsertionEffect unverzichtbar.

27.11 Fazit: Ein Hook für Library-Autoren

useInsertionEffect ist ein Hook, den Sie wahrscheinlich nie direkt verwenden werden. Aber Sie profitieren von ihm jedes Mal, wenn Sie styled-components, Emotion oder eine ähnliche Library nutzen.

Das ist ein Pattern, das sich durch React’s Design zieht: Hooks für Endnutzer (useState, useEffect) und Hooks für Library-Autoren (useSyncExternalStore, useInsertionEffect). Diese Trennung ermöglicht es, komplexe Probleme auf der richtigen Abstraktionsebene zu lösen.

Als Anwendungsentwickler ist Ihr wichtigstes Wissen über useInsertionEffect: 1. Er existiert für CSS-in-JS 2. Er läuft vor der Layout-Berechnung 3. Sie sollten ihn nicht direkt verwenden

Wenn Sie eine CSS-in-JS-Library bauen, ist useInsertionEffect Ihr bester Freund. Für alle anderen: Wissen Sie, dass er da ist, verstehen Sie, warum er existiert, und überlassen Sie die Nutzung den Experten, die ihn wirklich brauchen.