Design wechseln

React Compiler + shadcn/ui: Frontend-Entwicklung im Zeitalter der automatischen Optimierung

React DevTools geöffnet – und da rendert eine shadcn Data Table bei jedem Scroll neu, obwohl sich die Props gar nicht geändert haben. Über 20 handgeschriebene useMemo, trotzdem fallen Randfälle durch. Damals dachte ich: Wäre schön, wenn ein Tool diese Optimierungen automatisch übernähme.

Zwei Monate später erschien React Compiler v1.0. Kein Grübeln mehr, ob diese Funktion useMemo braucht, kein Sorgen, ob irgendwo ein useCallback fehlt. Der Compiler erledigt alles beim Build.

Was bedeutet das für shadcn/ui-Projekte? Bleiben die handgeschriebenen Memoization-Hooks? Gibt es Kompatibilitätsprobleme mit shadcn-Komponenten? Dieser Artikel teilt Praxiserfahrung aus den letzten Wochen.


TL;DR


1. Was ist der React Compiler?

Ehrlich gesagt ist der React Compiler nichts „Revolutionäres“ – eher ein Automatisierungstool für Performance-Optimierungen, die Sie sonst von Hand schreiben würden.

Früher musste man bei jedem Performance-Problem useMemo, useCallback und React.memo manuell setzen. Ein vergessener Hook, und die ganze Seite ruckelt. Die Memoization-Logik ist oft komplex: Abhängigkeiten analysieren, Grenzfälle abdecken, Überoptimierung vermeiden.

Der Ansatz des React Compilers: Diese Optimierungsmuster sind regelbasiert – der Compiler analysiert beim Build und fügt passende Memoization ein.

Beispiel Data Table – früher so:

// Handoptimierte Version (umständlich)
const columns = useMemo(() => [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
  // ... weitere Spaltendefinitionen
], []); // Dependency-Array selbst pflegen

const handleRowClick = useCallback((row) => {
  console.log('Clicked:', row);
}, []);

Mit Compiler können Sie das entfernen:

// Compiler-optimierte Version (schlank)
const columns = [
  {
    accessorKey: 'name',
    header: 'Name',
    cell: ({ row }) => row.original.name,
  },
];

const handleRowClick = (row) => {
  console.log('Clicked:', row);
};

Der Compiler analysiert Abhängigkeiten beim Build und entscheidet, ob memoized werden soll. Kein Grübeln mehr über „useMemo ja oder nein“.

Offiziell heißt es: „build-time performance optimization“. Kurz: Der Compiler übernimmt die Arbeit von React.memo.


2. React Compiler aktivieren: drei Wege

Mit Next.js 16 ist der Compiler bereits integriert. Andere Build-Tools brauchen extra Konfiguration.

Weg 1: Next.js 16 (am einfachsten)

Next.js 16 integriert den React Compiler standardmäßig. In next.config.js eine Zeile ergänzen:

// next.config.js
const nextConfig = {
  experimental: {
    reactCompiler: true, // Compiler aktivieren
  },
};

export default nextConfig;

Damit werden alle React-Komponenten im Projekt automatisch optimiert – ohne Codeänderungen.

Weg 2: Vite + React Compiler Plugin

Vite-Projekte brauchen ein Babel-Plugin:

npm install --save-dev babel-plugin-react-compiler

Dann in vite.config.ts konfigurieren:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          ['babel-plugin-react-compiler', {
            // Optional: Kompilierungsmodus
            // 'mode': 'optimize'
          }],
        ],
      },
    }),
  ],
});

Vite wendet den Compiler beim Build automatisch an.

Weg 3: Standalone Babel-Konfiguration (andere Tools)

Bei webpack, Rollup oder anderen Build-Tools das Plugin in der Babel-Konfiguration:

// .babelrc oder babel.config.json
{
  "plugins": [
    ["babel-plugin-react-compiler"]
  ]
}

Jedes Babel-basierte Build-Tool unterstützt den Compiler so.


3. shadcn/ui + React Compiler: Praxiserfahrung

Konkrete Ergebnisse: Ich habe ein shadcn/ui-Admin-Projekt mit etwa 40 Komponenten umgestellt. Insgesamt positiv, aber ein paar Details beachten.

Szenario 1: Re-Renders bei Dialog-Komponenten

shadcn Dialog ist typisch rein – gleiche Props, gleiches Ergebnis. Bei manueller Optimierung vergaß ich oft useCallback für onOpenChange.

Mit Compiler ist das automatisch gelöst. Der Compiler erkennt onOpenChange als stabile Funktion ohne externe Abhängigkeiten und memoized sie.

Im Test rendert die Elternkomponente beim Öffnen des Dialogs nicht mehr mit. Früher re-renderte der Parent jedes Mal, weil onOpenChange bei jedem Render neu erzeugt wurde.

Szenario 2: Form + Zod-Validierung

shadcn Form nutzt React Hook Form + Zod. Früher schrieb ich:

// Handoptimierte Version
const formSchema = useMemo(() => z.object({
  username: z.string().min(2, 'Mindestens 2 Zeichen'),
  email: z.string().email('Ungültiges E-Mail-Format'),
}), []);

const onSubmit = useCallback((values) => {
  console.log(values);
}, []);

Jetzt ohne useMemo/useCallback:

// Compiler-optimierte Version
const formSchema = z.object({
  username: z.string().min(2, 'Mindestens 2 Zeichen'),
  email: z.string().email('Ungültiges E-Mail-Format'),
});

const onSubmit = (values) => {
  console.log(values);
};

Der Compiler prüft, ob diese Funktionen stabil sind. Liefert das Zod-Schema jedes Mal dasselbe Objekt zurück (keine externen Variablen), memoized der Compiler automatisch.

Szenario 3: Data Table Rendering

shadcn Data Table basiert auf TanStack Table – ein Performance-Hotspot. Spaltendefinitionen, Sortier- und Filterfunktionen: überall manuelle Optimierung nötig.

Mit Compiler werden Spaltendefinitionen und Event-Handler automatisch optimiert. Gemessene Render-Zähler:

15/s
Render-Zähler (normal)
Source: Compiler vs. manuelle Optimierung (Praxistest)
  • Handoptimiert: beim Scrollen 15 Renders/Sekunde (normal)
  • Unoptimiert: beim Scrollen 45 Renders/Sekunde (schlecht)
  • Compiler: wie handoptimiert, 15 Renders/Sekunde

Gleiches Ergebnis – aber über 30 Zeilen manueller Optimierung weniger, deutlich besser lesbar.

Auswirkung auf die Bundle-Größe

Viele fürchten größere Bundles. In der Praxis minimal – die Optimierung passiert zur Build-Zeit, kein extra Runtime-Code.

Vergleich:

  • Ohne Compiler: Bundle 142 KB
  • Mit Compiler: Bundle 144 KB

Plus 2 KB durch eingefügte Memoization-Logik. Dafür entfallen handgeschriebene useMemo/useCallback (die ohnehin im Bundle lagen). Gesamtwirkung gering.


4. Migration: diese Fallstricke vermeiden

Der Compiler ist nicht perfekt. Bei der Migration auf Folgendes achten:

1. Benennungskonventionen sind wichtig

Der Compiler leitet Abhängigkeiten aus Variablennamen ab. Bei Code wie:

// ❌ Compiler analysiert möglicherweise ungenau
function MyComponent(props) {
  return <div>{props.data.name}</div>;
}

kann props.data nicht korrekt erkannt werden. Besser:

// ✅ Klare Benennung
function MyComponent({ data }) {
  return <div>{data.name}</div>;
}

So bewertet der Compiler die Abhängigkeit von data genauer.

Auch in der React-Dokumentation empfohlen: Destructuring macht Code klarer und hilft dem Compiler.

2. ESLint-Regeln ändern sich

Mit react-hooks/exhaustive-deps im Projekt ändern sich die Meldungen nach Compiler-Aktivierung.

Früher meldete ESLint fehlende useMemo-Abhängigkeiten. Jetzt übernimmt der Compiler das – die Regel verliert an Bedeutung.

ESLint anpassen: exhaustive-deps auf „warn“ setzen oder deaktivieren. Der Compiler behandelt Abhängigkeiten – die Regel wird oft nur noch Rauschen.

// .eslintrc
{
  "rules": {
    "react-hooks/exhaustive-deps": "off" // Compiler behandelt Abhängigkeiten
  }
}

3. Drittanbieter-Kompatibilität

Manche Bibliotheken sind inkompatibel – besonders mit komplexer Side-Effect-Logik.

Bei Build-Fehlern oder Laufzeitproblemen nach Aktivierung:

  1. Fehlermeldung prüfen – meist Benennung oder Logik einer Komponente
  2. Compiler für diese Komponente deaktivieren (Kommentar 'use no memo')
  3. Schrittweise weiter eingrenzen

Bei mir: eine Drag-and-Drop-Bibliothek inkompatibel. Lösung – Compiler für die Drag-Komponente deaktivieren:

'use no memo'; // Compiler: diese Komponente nicht optimieren

function DraggableList() {
  // Drag-Logik...
}

Der Compiler überspringt diese Komponente.

4. Wann Compiler deaktivieren?

Für die meisten Komponenten kein Problem. In diesen Fällen eher deaktivieren:

  • Komplexe Side Effects: Timer, Animationen, direkte DOM-Manipulation – Analyse kann ungenau sein
  • Inkompatible Drittanbieter: komplexe interne Logik, Fehlinterpretation möglich
  • Performance verschlechtert sich: selten – übermäßige Memoization kann Speicher erhöhen

Deaktivierung: 'use no memo'-Kommentar am Komponentenanfang.


5. Vom manuellen zum automatischen Optimieren: Migrationsprotokoll

Konkreter Ablauf: Admin-Dashboard, über 40 shadcn/ui-Komponenten.

Code vor der Migration

Etwa 50 handgeschriebene useMemo/useCallback. Data Table am komplexesten – Spalten, Sortierung, Filter, überall manuell.

Umständlicher Code:

// Data Table vor Migration (manuell optimiert)
const columns = useMemo(() => [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
  // ...weitere Spalten
], []);

const sorting = useMemo(() => [{ id: 'name', desc: true }], []);

const handleSortingChange = useCallback((updater) => {
  setSorting(updater);
}, []);

Code nach der Migration

Nach Compiler-Aktivierung alle manuellen Optimierungen entfernt:

// Data Table nach Migration (Compiler-optimiert)
const columns = [
  { accessorKey: 'id', header: 'ID' },
  { accessorKey: 'name', header: 'Name' },
];

const sorting = [{ id: 'name', desc: true }];

const handleSortingChange = (updater) => {
  setSorting(updater);
};

Schlanker und lesbarer. Kein Prüfen jedes useMemo-Dependency-Arrays mehr.

Performance-Vergleich

Lighthouse-Scores:

  • Vor Migration: Performance 82, LCP 1,8 s
  • Nach Migration: Performance 85, LCP 1,6 s

Leicht besser – teils durch Entfernen überflüssiger manueller Optimierung; der Compiler memoized nur, wo nötig.

Render-Zeit beim Data-Table-Scrollen:

  • Handoptimiert: durchschnittlich 12 ms
  • Compiler: durchschnittlich 10 ms

Ähnlich, leicht besser. Die Compiler-Logik ist solide.

Team-Feedback

Nach der Migration im Team:

  • „Kein Grübeln mehr über memo – erleichtert den Alltag“
  • „Ohne die ganzen useMemo wirkt der Code viel aufgeräumter“
  • „Eine Komponente inkompatibel – ‚use no memo‘, fertig“

Überwiegend positiv. Weniger mentale Last – nicht bei jeder Funktion „memoize ja oder nein?“.


Fazit

Der React Compiler ist ein wichtiges Update für das React-Ökosystem. Für shadcn/ui-Projekte lohnt sich die Aktivierung:

  1. Automatische Performance-Optimierung: kein manuelles useMemo/useCallback – der Compiler erledigt es beim Build
  2. Schlankerer Code: weniger Optimierungs-Boilerplate, bessere Lesbarkeit
  3. Weniger mentale Last: kein ständiges „memoize oder nicht?“

Bei der Migration beachten:

  • Benennung: Props destructuren, props.data vermeiden
  • ESLint: exhaustive-deps anpassen
  • Drittanbieter: bei Problemen 'use no memo' nutzen

In Next.js-16-Projekten zuerst ausprobieren – eingebaut, minimal konfiguriert. Andere Build-Tools: Vite-Konfiguration als Vorlage, schrittweise migrieren.

Nach dem Compiler fühlt sich React anders an – eher wie „normales“ JavaScript, ohne ständig an Performance-Details zu denken. Das ist angenehm.


FAQ

FAQ

Erhöht der React Compiler die Bundle-Größe?
Nicht nennenswert. In unseren Tests wuchs das Bundle nach Aktivierung des Compilers nur um etwa 2 KB – hauptsächlich durch eingefügte Memoization-Logik. Dafür entfallen handgeschriebene useMemo/useCallback. Die Gesamtwirkung ist minimal.
Sind shadcn/ui-Komponenten mit dem React Compiler kompatibel?
Die meisten shadcn/ui-Komponenten sind reine Komponenten – der Compiler erkennt Abhängigkeiten gut und optimiert zuverlässig. Bei einigen Komponenten mit komplexer interner Logik kann der Kommentar ‚use no memo‘ nötig sein, um den Compiler zu deaktivieren. Schrittweise migrieren und bei Problemen gezielt deaktivieren.
Soll man handgeschriebene useMemo/useCallback nach Compiler-Aktivierung entfernen?
Ja, empfohlen. Der Compiler fügt Memoization automatisch ein, wo nötig. Handgeschriebene useMemo/useCallback werden oft überflüssig. Nach der Migration ist der Code schlanker, die Performance gleichwertig oder besser.
In welchen Szenarien sollte man den Compiler deaktivieren?
Bei komplexer Side-Effect-Logik (Timer, Animationen, direkte DOM-Manipulation), inkompatiblen Drittanbieter-Bibliotheken oder seltenen Fällen, in denen die Performance schlechter wird. Deaktivierung: Kommentar ‚use no memo‘ am Anfang der Komponente.
Welche Anforderungen stellt der Compiler an die Benennung?
Props destructuren, statt props.data direkt zu nutzen. Beispiel: const { data } = props oder function MyComponent({ data }). So analysiert der Compiler Abhängigkeiten genauer.
Wie aktiviert man den Compiler in Next.js 16 und Vite?
Next.js 16: In next.config.js experimental: { reactCompiler: true } setzen. Vite: babel-plugin-react-compiler installieren und in vite.config.ts unter react plugin → babel.plugins konfigurieren.

7 Min. Lesezeit · Veröffentlicht am: 31. März 2026 · Aktualisiert am: 13. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog