2026

22. April 2026

Der React Compiler nach achtzehn Monaten: Die Entwicklung, die Debatten und was als Nächstes kommt

Der React Compiler erschien Ende 2024 zusammen mit React 19 in der öffentlichen Beta und erreichte im Oktober 2025 die Version 1.0. Achtzehn Monate nach dieser Beta hat das Ökosystem die vorhersagbaren Phasen durchlaufen: Framework-Integration, Reife des Toolings, Community-Debatten. Eine Rückschau und was das React-Team als Nächstes angedeutet hat.

S
Sascha Becker
Author

14 Min. Lesezeit

Der React Compiler nach achtzehn Monaten: Die Entwicklung, die Debatten und was als Nächstes kommt

React 19 erschien Ende 2024, und der React Compiler kam zeitgleich in der öffentlichen Beta. Er lief bei Meta bereits in Produktion, das React-Team empfahl ihn ausdrücklich zur Nutzung, und im Oktober 2025 wurde schließlich Version 1.0 markiert. Achtzehn Monate nach dieser Beta hat das Ökosystem die Phasen durchlaufen, die man bei jeder Plattform-Änderung dieses Umfangs erwarten würde: Ankündigung, Framework-Integration, Reife des Toolings, und dann die langsamere, unordentlichere Arbeit, zu diskutieren, was das Ding eigentlich bedeutet.

Dieser Beitrag ist eine Rückschau auf diesen Verlauf und eine Lesart dessen, was das React-Team für die Zukunft signalisiert hat. Es ist kein Einführungstext, auch wenn die Einführungsteile als Referenz enthalten sind. Ich habe selbst keine große Produktions-Migration durchgeführt. Was folgt, basiert auf der öffentlichen Dokumentation, RFCs, Talks des React-Teams und Early-Adopter-Berichten, die am Ende verlinkt sind.

Der Achtzehn-Monats-Bogen

Seit der Beta-Ankündigung im Oktober 2024 hat die Geschichte des Compilers eine vorhersagbare Form: Release, Integration, Tooling, Debatte.

Release und frühe Integration. In den Monaten nach dem Erscheinen von React 19 war die praktische Oberfläche des Compilers meist Konfiguration. Next.js, Expo, TanStack Start und die Vite-basierten Frameworks bauten ihn jeweils in ihre Build-Pipeline ein. Für neue Apps wurde die Geschichte "er ist standardmäßig an, und du kannst ihn ausschalten, wenn du musst." Für bestehende Apps entstand ein Migrationspfad rund um das ESLint-Plugin als Frühindikator für Reife.

Die stille Mitte. Die Mitte des Bogens war ruhiger, als der anfängliche Hype es vermuten ließ. Teams, die früh migrierten, taten das mit mehr Skepsis als Fanfare, und die Erfolge, die sie berichteten, waren von der langweiligen Sorte: weniger Re-Render-Bugs im Code Review, ein Rückgang in der "warum ist das langsam"-Bug-Kategorie, eine Codebasis, die aufhörte, neuen Memoization-Wildwuchs anzusammeln. Die dramatischen Benchmark-Posts, auf die sich die Community eingestellt hatte, kamen nie wirklich, weil der größte Einfluss des Compilers ist, Bugs zu vermeiden, nicht Schlagzeilen zu produzieren.

Die Auseinandersetzung mit dem Ökosystem. Ende 2025 verlagerte sich das Gespräch von "sollten wir migrieren" zu "was machen wir mit den Bibliotheken, die ihn brechen." Dort stecken die meisten Teams noch fest. Die Rules of React, die schon immer galten, wurden zur Build-Zeit erzwingbar, und überraschend viele Teile des Ökosystems hatten sie stillschweigend gebogen. Ältere State-Libraries, Legacy-Formulare, einige Drag-and-Drop-Implementierungen, eine Handvoll beliebter Utility-Hooks. Der Compiler hat sie nicht kaputtgemacht; er hat sichtbar gemacht, dass sie die ganze Zeit über kaputt waren.

Der aktuelle Stand in einem Satz: Greenfield ist gelöst, Brownfield ist ein Projekt.

Was der Compiler tut

Kurz, als Referenz. Der Compiler ist eine Build-Zeit-Transformation. Er liest deine Komponenten, nimmt an, dass sie den Rules of React folgen, und fügt Memoization dort ein, wo sie hilft. Es gibt keinen Laufzeit-Overhead vom Compiler selbst; die Ausgabe ist einfacher React-Code. Die Granularität ist besser als das, was ein Mensch schreibt: Wo ein handgeschriebenes useMemo ein ganzes abgeleitetes Objekt memoiziert, kann der Compiler einzelne Sub-Ausdrücke darin memoizieren, sodass die Änderung eines Felds die anderen nicht invalidiert.

In der Praxis wird aus Code wie diesem:

tsx
const DashboardRow = memo(({ entity, onSelect }: Props) => {
const formatted = useMemo(
() => ({
label: formatLabel(entity),
total: entity.items.reduce((sum, i) => sum + i.value, 0),
status: entity.state === "active" ? "green" : "red",
}),
[entity],
);
const handleClick = useCallback(() => {
onSelect(entity.id);
}, [entity.id, onSelect]);
return (
<Row onClick={handleClick}>
<Label color={formatted.status}>{formatted.label}</Label>
<Total>{formatted.total}</Total>
</Row>
);
});

in einer Codebasis mit aktiviertem Compiler Code wie dieser:

tsx
function DashboardRow({ entity, onSelect }: Props) {
const label = formatLabel(entity);
const total = entity.items.reduce((sum, i) => sum + i.value, 0);
const status = entity.state === "active" ? "green" : "red";
return (
<Row onClick={() => onSelect(entity.id)}>
<Label color={status}>{label}</Label>
<Total>{total}</Total>
</Row>
);
}

Der memo-Wrapper, das useMemo, das useCallback: weg. Der sichtbarste Effekt während Interaktionen ist eine Änderung des Skalierungsverhaltens von Re-Renders. Der klassische Fehlerfall ist eine Liste, in der eine falsche Abhängigkeit in einem useCallback des Parents jede Zeile bei jedem Tastendruck neu rendern lässt; die Kosten skalieren mit der Listengröße, bis Eingabe-Lag sichtbar wird. Der Compiler entfernt diese Skalierung. Zeilen, deren Props sich nicht geändert haben, bleiben unabhängig von der Listengröße unberührt.

Die Kosten, der Vollständigkeit halber: Die Build-Zeit steigt in öffentlichen Benchmarks um zweistellige Prozentwerte (inkrementelle Builds sind weitgehend unbeeinflusst), und die Bundle-Größe steigt um einen niedrigen einstelligen Prozentsatz durch inline eingefügte Memoization-Helfer. Beides ist projektspezifisch; trau keiner exakten Zahl, die du liest, ohne dein eigenes Projekt zu messen.

Wo der Compiler scheitert

Achtzehn Monate Produktionseinsatz haben die Liste dessen geschärft, was der Compiler nicht anfasst. Die vier Muster unten tauchen in jeder Migrations-Aufzeichnung auf, die ich gelesen habe.

1. Props oder Closures während des Renderings mutieren

tsx
function Row({ entity }: Props) {
entity.lastSeen = Date.now(); // Mutation während des Renderings
return <span>{entity.name}</span>;
}

Der Compiler verweigert die Transformation. Korrigiere den Code.

2. Ein Ref während des Renderings lesen

tsx
function Tooltip() {
const ref = useRef<HTMLDivElement>(null);
const width = ref.current?.offsetWidth; // liest Ref im Render-Body
return <div ref={ref}>{width}px</div>;
}

Verschiebe das Lesen in einen Effect oder useLayoutEffect. Der Compiler markiert es, schreibt es aber nicht um.

3. Klassen-Komponenten (Legacy)

Klassen-Komponenten werden überhaupt nicht kompiliert. Wenn du noch React.Component-Subklassen hast, laufen sie wie immer. In neuen Projekten selten ein Problem, in älteren Codebasen gut zu wissen.

4. Nicht unterstützte Syntax in sonst sauberen Komponenten

Das ist der Fehlermodus, der Leute überrascht. Der Compiler steigt bei einzelnen Syntax-Mustern aus, die er nicht analysieren kann, und der Ausstieg ist still, solange die passende Lint-Regel nicht aktiv ist. Die Muster, die am häufigsten auftauchen:

tsx
// Eine destrukturierte Prop neu zuweisen
function Field({ value }: Props) {
value = value ?? defaultValue; // Compiler überspringt diese Funktion
return <input defaultValue={value} />;
}
// Bedingungen, optional chaining oder Nullish-Coalescing innerhalb von try/catch
async function load(url: string) {
try {
const res = await fetch(url);
const data = ((await res.json()) as Data | undefined) ?? {};
if (data.ok) setResult(data); // wird ebenfalls übersprungen
} catch (e) {
logError(e);
}
}

Beides kompiliert als plain React. Keiner der beiden Fälle ist ein Laufzeit-Bug. Die Falle ist, dass die umliegende Komponente still aufhört, memoiziert zu werden, und du es über eine Re-Render-Regression mitbekommst, nach der kein Profiler gesucht hat.

Die Lösung ist die Regel react-hooks/unsupported-syntax, die in eslint-plugin-react-hooks v6+ ausgeliefert wird (das Paket, das Ende 2025 eslint-plugin-react-compiler absorbiert hat). Sie ist ab v7 in recommended enthalten, allerdings als Warnung. CI-Konfigurationen, die nur bei Errors fehlschlagen, gehen daran vorbei. Sie auf error zu heben ist der Schritt, der still übersprungene Komponenten tatsächlich zum Build-Fehler macht.

5. Die "use no memo"-Escape-Hatch

Manchmal liegt der Compiler falsch, oder der Aufwand, eine Komponente compiler-sicher zu machen, lohnt sich noch nicht. Du kannst pro Funktion aussteigen:

tsx
function ComplicatedLegacyThing() {
"use no memo";
// Compiler überspringt diese Funktion, behandelt sie wie plain React
...
}

Der Migrationspfad, den die meisten Teams gehen

Auf Basis der React-Docs und der Integrationsanleitungen für Next.js, Expo und TanStack Start ist die üblicherweise empfohlene Reihenfolge kurz:

  1. React aktualisieren auf eine Version, die den Compiler unterstützt (React 19 oder höher).
  2. ESLint-Plugin einsetzen und Regelschweregrade anpassen. Seit Ende 2025 wohnen die Regeln in eslint-plugin-react-hooks v6+ (das eigenständige eslint-plugin-react-compiler wurde deprecated und einverleibt). Wenn das Projekt bereits ein Framework-Preset wie eslint-config-next v16+ verwendet, ist das v7-Plugin transitiv vorhanden und der recommended-Satz aktiv, für die meisten Leser ist also nichts neu zu installieren. Der eigentliche Schritt ist, Regeln von warn auf error zu heben, allen voran react-hooks/unsupported-syntax, damit still übersprungene Komponenten als Lint-Fehler auftauchen statt als Performance-Regressionen. Reparieren, was sie finden, bevor man die Compiler-Konfiguration anfasst.
  3. Compiler im annotation-gesteuerten Modus aktivieren. Eine oder zwei Leaf-Komponenten zuerst kompilieren und beobachten.
  4. Auf Inferenz-Modus umschalten. Den Compiler entscheiden lassen, welche Dateien er übernimmt. Tests laufen lassen, eine echte Interaktion profilen.
  5. Manuelle Memoization entfernen. useMemo-, useCallback-, React.memo-Aufrufe, die der Compiler jetzt übernimmt, lassen sich in großen Batches löschen.
  6. Strict-Mode in Betracht ziehen, sobald die Codebasis sauber ist, damit Verletzungen werfen statt still zu überspringen.

Teams, die direkt zu Schritt 5 springen, produzieren meist einen riesigen PR, der wochenlang offen liegt. Schritte 1 und 2 lassen sich unabhängig ausliefern und verbessern die Code-Qualität für sich.

Was "die Regeln aktivieren" in der Praxis bedeutet

Ein paar Dinge werden erst sichtbar, wenn man den strikten Regelsatz gegen eine bestehende Codebasis schaltet, statt die Regelliste nur zu lesen.

Ein konkreter Startpunkt für die strikte Konfiguration, eingefügt in eine flache eslint.config.mjs:

js
{
rules: {
"react-hooks/unsupported-syntax": "error",
"react-hooks/exhaustive-deps": "error",
"react-hooks/incompatible-library": "error",
"react-hooks/todo": "error",
"react-hooks/syntax": "error",
"react-hooks/capitalized-calls": "error",
"react-hooks/rule-suppression": "error",
"react-hooks/no-deriving-state-in-effects": "error",
"react-hooks/void-use-memo": "error",
"react-hooks/automatic-effect-dependencies": "error",
"react-hooks/memoized-effect-dependencies": "error",
"react-hooks/hooks": "error",
},
}

Die ersten drei heben Regeln, die recommended bereits als warn mitliefert. Der Rest sind die standardmäßig deaktivierten Compiler-Regeln. Kein Plugin zu installieren, wenn man auf eslint-config-next v16+ oder einem anderen modernen Framework-Preset sitzt, das eslint-plugin-react-hooks v7 mitbringt.

react-hooks/todo fängt das meiste. Sie ist nicht standardmäßig aktiv und derzeit nicht auf react.dev dokumentiert, aber sie ist die Regel, die den breitesten Satz an Compiler-internen Lowering-Fehlern aufdeckt, also die Muster, die der Compiler schlicht noch nicht beigebracht bekommen hat. Zwei Beispiele aus echten Codebasen:

tsx
// Mutierter Counter, in .map()-Lambdas eingefangen
let globalIndex = 0;
return (
<>
{groupA.map((row, i) => render(row, i, globalIndex++))}
{groupB.map((row, i) => render(row, i, globalIndex++))}
</>
);
// Dynamisches import() innerhalb eines Effects
useEffect(() => {
void import("heavy-lib").then(({ default: lib }) => {
/* ... */
});
}, []);

Beide Snippets kompilieren und laufen, und beide nehmen die umliegende Komponente still aus dem Zugriff des Compilers. Die Fixes sind mechanisch: den mutierten Counter durch vorab berechnete Offsets ersetzen (groupB.map((row, i) => render(row, i, offsetB + i))) und das dynamische Import in ein modul-level zwischengespeichertes Promise hochziehen, sodass der Import-Ausdruck nicht mehr im Komponenten-Body lebt. Diese Regel anzuschalten lohnt sich für das schärfste Signal, mit der Einschränkung, dass die Ausgabe lauter ist als die von unsupported-syntax.

Stille Aussteiger kaskadieren. Sobald eine Komponente aussteigt, bringen nachgelagerte Regeln ihre Funde an derselben Komponente manchmal nicht mehr zur Oberfläche, die Analyse kann beim ersten Fehler stoppen. Den vorgelagerten Aussteiger zu fixen kann sofort einen zweiten Fund freilegen. Lint-Cleanup ist iterativ, nicht one-shot. Der erste Lauf gibt dir die Anzahl, die folgenden Läufe geben dir die Wahrheit.

Der meiste Produktionscode passiert die Regeln. Die anderen Compiler-Regeln, die standardmäßig aus sind (syntax, capitalized-calls, rule-suppression, no-deriving-state-in-effects, void-use-memo, die Effect-Deps-Regeln) sind in Codebasen mit gewöhnlichen React-Idiomen meist still. Trotzdem aktivieren, die False-Positive-Rate ist niedrig und die Fläche, auf der sie etwas fangen, ist genau die Fläche, die du dir nicht still leisten kannst.

Was noch umstritten ist

Drei Dinge, die die Community nach achtzehn Monaten noch nicht geklärt hat.

Rules of React als erzwingbarer Vertrag

Die Rules of React gab es schon vor dem Compiler, aber sie waren eine Empfehlung. Der Compiler macht sie zu einer Build-Zeit-Grenze: Verletze sie, und deine Komponente steigt still aus der Memoization aus. Eine lautstarke Minderheit argumentiert, das dränge still ein strikteres Programmiermodell auf, als React selbst je versprochen habe. Das Gegenargument ist, dass die Rules nie wirklich optional waren; der Compiler macht lediglich die Konsequenzen sichtbar. Beide Lager haben teilweise recht, und die Spannung zeigt sich am deutlichsten in älteren Bibliotheken, die vor der Niederschrift der Rules entstanden sind.

"use no memo" als dauerhafte technische Schuld

Die Escape-Hatch ist leicht zu greifen, und das beunruhigt die Leute, die gesehen haben, wie sich solche Marker in Codebasen über Jahre ansammeln. Die üblichen Vergleiche sind @ts-ignore und // eslint-disable: nützliche Ventile, die schlecht altern, wenn sie nicht mehr überprüft werden. Andere argumentieren, die Direktive sei gerade deshalb tragend, weil sie erlaubt, ohne vollständigen Refactor auszuliefern, und sie als dauerhaften Marker zu behandeln sei Missbrauch, keine Eigenschaft des Features. Unumstritten ist inzwischen, dass die "use no memo"-Anzahl eine legitime Codebasis-Gesundheitsmetrik ist.

Compiler gegen Laufzeit-Optimierer

Für die meisten Apps reicht der Compiler. Für listen-lastige Spezial-UIs (Trading-Dashboards, Log-Viewer, Spreadsheets) gewinnt ein block-basierter Reconciler wie Million.js noch messbar. Die beiden Ebenen lösen unterschiedliche Probleme: Der Compiler reduziert, wie oft Komponenten neu rendern; Laufzeit-Optimierer verändern, wie schnell jedes Re-Rendering ist. Manche Teams nutzen beides. Die Interaktion zwischen ihnen funktioniert in der Praxis, ist aber nirgendwo so dokumentiert, dass es sich abgeschlossen anfühlt. Ich vermute, hier wird irgendwann jemand den definitiven Post schreiben.

Was als Nächstes kommt

Die öffentlichen Signale vom React-Team, aus Meta-Engineering-Posts und aus aktiven RFCs deuten in fünf Richtungen. Keine davon sind Versprechen; nimm sie als Richtungsangabe.

Feingranularere Kompilierungskontrolle

Die aktuellen Modi sind grob: an, aus, annotation-gesteuert. Was Teams zu wollen scheinen, sind Hinweise pro Komponente, diesen aggressiv kompilieren, jenen konservativ, diese Legacy-Insel gar nicht, plus besseres Tooling, um zu sehen, was der Compiler tatsächlich getan hat. Erwarte Direktiven, die den aktuellen Alles-oder-Nichts-Tradeoff verfeinern.

Compiler-bewusste Server Components

RSC liefert bereits memoization-freundliche Ausgabe für Client-Islands. Der nächste Schritt ist die Straffung der Serialisierungsgrenze, damit die Payload, die der Browser hydraten muss, kleiner wird, wenn der Compiler beweisen kann, dass ein Wert die Grenze nicht überqueren muss. Dort liegen die größten Performance-Gewinne für echte Apps: Hydrations-Kosten zu reduzieren, zählt mehr als Re-Render-Zahlen zu reduzieren, weil Hydration das ist, was User bei Cold Loads spüren.

Konvergenz von useEvent

Das lang diskutierte Primitive für stabile Event-Callbacks, die den neuesten State lesen, steckt seit Jahren im RFC. Die Purity-Analyse des Compilers ist das Teil, das seine Semantik beweisbar macht, weshalb der Vorschlag vor dem Compiler stockte. Erwartung: Er wird in irgendeiner Form ausgeliefert.

React Native

Natives View-Diffing ist teurer als Web-Reconciliation, daher zählt Auto-Memoization dort überproportional. Expo hat Compiler-Unterstützung ausgeliefert; React Native selbst war langsamer. Der Druck auf Parität wächst, weil der Nutzen pro Komponente größer ist.

Developer-Tooling

Das fehlende Teil ist ein DevTools-Panel, das pro Komponente zeigt, was der Compiler getan hat und warum. Derzeit sagt das ESLint-Plugin, was übersprungen wurde; ein Visualizer für das, was optimiert wurde, würde die Arbeit des Compilers sichtbar machen, ähnlich wie der RSC-Tree-Visualizer. Es steht auf keiner öffentlichen Roadmap, die ich kenne, aber es ist das offensichtlich nächste Werkzeug, und das könnte ein Community-Plugin liefern, bevor das React-Team es tut.

Mein Blick auf die Zukunft

Achtzehn Monate später wird das Vermächtnis des Compilers nicht seine Benchmark-Zahlen sein. Es wird die Bug-Kategorie sein, die er abgeschafft hat. "Du hast eine useCallback-Dependency vergessen" ist keine Diskussion mehr. Genauso wenig "sollte diese Komponente memoiziert werden?" Die Antwort ist ja, immer, und der Compiler kümmert sich darum.

Die interessantere Frage ist, was aus den Rules of React wird, jetzt wo ein Compiler sie erzwingt. Jahrelang waren sie eine Liste von Dingen, die guter React-Code zufällig tat; jetzt sind sie ein Build-Zeit-Vertrag, der Code ablehnt, der sich nicht daran hält. Einige der ältesten Bibliotheken des Ökosystems passen sich noch an. Das nächste Jahr der Compiler-Geschichte wird wahrscheinlich weniger um den Compiler selbst gehen und mehr darum, wie das Library-Ökosystem aussieht, nachdem alles compiler-sicher gemacht wurde.

Für neue Projekte ist die Entscheidung leicht: einschalten. Für bestehende Projekte ist die Entscheidung, ob man den ältesten Code repariert oder "use no memo" als Commitment-Device ausliefert. Beides sind legitime Tradeoffs; beide sind es wert, bewusst getroffen zu werden.

Wenn du 2026 ein useMemo oder useCallback siehst, behandle es wie eine manuelle for-Schleife in modernem JavaScript. Meist in Ordnung. Gelegentlich notwendig. Meist ein Zeichen, dass der Autor das geschrieben hat, bevor bessere Werkzeuge existierten.


S
Geschrieben von
Sascha Becker
Weitere Artikel