Warum JSX, Hooks und das React-Ökosystem keine modernen Antworten auf heutige Frontend-Probleme sind
Es gibt Technologien, die setzen sich durch, weil sie besser sind. Und es gibt Technologien, die setzen sich durch, weil sie früh da waren, ein großes Unternehmen dahinter stand und eine kritische Masse erreicht wurde, bevor Alternativen reifen konnten.
React gehört ziemlich eindeutig zur zweiten Kategorie. Und ja — dieser Artikel ist Bashing. Bewusstes, begründetes Bashing. Nicht aus Prinzip, sondern weil die Kritik an React in der Branche oft zu leise geführt wird, während die Pfadabhängigkeit immer stärker wird.
Ich habe React nie produktiv eingesetzt. Nicht aus Ignoranz, nicht aus Bequemlichkeit — sondern weil mir schon beim ersten Blick etwas fundamental falsch vorkam: die Vermischung von Struktur und Logik. Und je tiefer man schaut, desto mehr bestätigt sich dieser erste Eindruck.
JSX — der Bruch mit einer bewährten Trennung
Frontend-Entwicklung hatte lange eine klare Aufteilung: HTML für Struktur, CSS für Darstellung, JavaScript für Logik. Diese Trennung war kein Zufall, sondern ein gewachsenes Architekturprinzip. Sie machte Code lesbar, testbar und wartbar. Generationen von Entwicklern haben diese Trennung gelernt, verinnerlicht und weitergegeben.
Dann kam React — und hat sie über Bord geworfen.
function UserCard({ name, role, avatar }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div className={`card ${isExpanded ? 'card--expanded' : ''}`}>
<img src={avatar} alt={name} className="card__avatar" />
<div className="card__content">
<h3 className="card__name">{name}</h3>
<span className="card__role">{role}</span>
{isExpanded && (
<div className="card__details">
<p>Details werden geladen...</p>
</div>
)}
</div>
<button onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? 'Weniger' : 'Mehr'}
</button>
</div>
);
}
State-Management, Event-Handling, bedingtes Rendering, CSS-Klassen-Logik, Markup-Struktur — alles in einer Funktion. Das wird als „Kolokation” verkauft: Was zusammengehört, lebt zusammen. Klingt erstmal schlüssig.
Aber schau genauer hin. className statt class, weil class in JavaScript reserviert ist. onClick statt onclick, weil React sein eigenes Event-System hat. {isExpanded && (...)} statt einer Template-Direktive, weil JSX keine Kontrollstrukturen kennt — du musst JavaScript-Tricks verwenden, um bedingtes Rendering auszudrücken. Ternary-Operatoren stapeln sich, sobald es komplexer wird.
In Svelte sieht dasselbe so aus:
<script>
let { name, role, avatar } = $props();
let isExpanded = $state(false);
</script>
<div class="card" class:card--expanded={isExpanded}>
<img src={avatar} alt={name} class="card__avatar" />
<div class="card__content">
<h3 class="card__name">{name}</h3>
<span class="card__role">{role}</span>
{#if isExpanded}
<div class="card__details">
<p>Details werden geladen...</p>
</div>
{/if}
</div>
<button onclick={() => isExpanded = !isExpanded}>
{isExpanded ? 'Weniger' : 'Mehr'}
</button>
</div>
<style>
.card { /* scoped per default */ }
</style>
Logik im <script>-Block. Markup als echtes HTML mit class statt className. Bedingtes Rendering mit {#if} statt verschachtelten Ternaries. Styling scoped per Default. Drei klar getrennte Bereiche, die trotzdem in einer Datei leben.
Der Unterschied ist nicht kosmetisch. Svelte-Templates sind valides HTML mit Erweiterungen. JSX ist JavaScript, das so tut, als wäre es HTML. Das hat Konsequenzen für Tooling, Testing, Debugging und die kognitive Last beim Lesen von Code.
Man kann JSX als ergonomisch empfinden. Viele Entwickler tun das. Aber „ich finde es produktiv” ist kein Architekturargument. Und die Frage ist nicht, ob JSX funktioniert — sondern ob die Vermischung von Verantwortlichkeiten langfristig der bessere Ansatz ist. Die Entwicklung moderner Frameworks deutet darauf hin, dass die Antwort nein lautet.
Die konkreten Kosten von JSX
JSX erzeugt Probleme, die über Geschmacksfragen hinausgehen:
Build-Tooling-Zwang. JSX ist weder HTML noch JavaScript — es erfordert immer einen Transpiler wie Babel oder SWC. Jede React-App braucht eine vollständige Build-Pipeline mit Bundler, Dev-Server und Konfiguration. Ohne Build-Step bricht alles zusammen. Du kannst kein JSX in einen Browser werfen und schauen, was passiert. Du kannst es nicht in einer einfachen HTML-Datei testen. Du brauchst immer die gesamte Toolchain. Svelte- und Astro-Templates haben zwar auch einen Compile-Schritt, aber ihre Templates sind strukturell valides HTML — du kannst sie lesen, ohne den Compiler im Kopf laufen zu lassen.
Editor-Probleme. Keine native IntelliSense für JSX in Editoren ohne spezielle Extensions. Syntax-Highlighting ist oft fehlerhaft, besonders bei tief verschachtelten Tags und Expressions. Find/Replace über HTML-ähnliche Strukturen funktioniert schlecht, weil JSX technisch JS-Ausdrücke sind — dein Editor sieht Funktionsaufrufe, keine Tags. Linting-Regeln wie react-in-jsx-scope erzeugen künstliche Fehler, die erst konfiguriert werden müssen. Vor React 17 musstest du import React from 'react' in jeder einzelnen Datei schreiben, die JSX enthält — auch wenn du React nie direkt verwendest. Ein Framework, das dich zwingt, sich selbst zu importieren, hat ein Designproblem.
Kein Browser versteht JSX. „View Source” zeigt keine echte Struktur. Debugging im Browser-Inspector wird zur Rekonstruktionsarbeit: Was du im Code schreibst und was im DOM landet, sind zwei verschiedene Dinge. React DevTools helfen, aber sie sind ein Pflaster auf einem Problem, das es ohne JSX nicht gäbe. Bei SSR kommen Hydration-Probleme dazu — der Server rendert HTML, der Client muss es „übernehmen”, und wenn auch nur ein Detail nicht übereinstimmt, wirft React den gesamten Server-gerenderten Baum weg und rendert komplett neu. Das ist nicht nur ein Performance-Problem, es ist ein Architektur-Antipattern.
Semantik geht verloren. JSX ermutigt zu Custom-Elementen: <MyButton> statt <button>, <MyDialog> statt <dialog>, <MyDropdown> statt <select>. Das sieht in der Codebase sauber aus, verliert aber native ARIA-Rollen, Browser-Behavior, Keyboard-Navigation und Fokus-Management. All das muss dann manuell nachgebaut werden — und wird es in der Praxis oft nicht, oder nicht vollständig. Das Ergebnis: React-Anwendungen, die für Screenreader-Nutzer unbenutzbar sind, weil ein Team dachte, <div onClick> sei ein akzeptabler Button.
Versionskonflikte und Breaking Changes. Die JSX-Transform hat sich über die Jahre mehrfach geändert. Die neue Transform-Variante ab React 17 kollidiert mit alten ESLint-Setups und TypeScript-Konfigurationen. Migrationen von className zu data-*-Attributen brechen Legacy-Code. Jede React-Version bringt subtile Änderungen in der JSX-Verarbeitung, die sich durch die gesamte Toolchain ziehen — Babel-Plugins, TypeScript-Compiler, ESLint-Rules, Prettier-Formatter, alle müssen synchron aktualisiert werden.
| Problem | JSX (React) | Svelte/Astro-Templates |
|---|---|---|
| Browser-Verständnis | Transpilation zwingend | Strukturell valides HTML |
| Editor-Support | Extensions erforderlich | Native HTML/JS-Support |
| Testing | Framework-Wrapper nötig | DOM/JS nativ testbar |
| Debugging | Rekonstruktion nötig | Direkt im Inspector |
| Build-Overhead | Immer vollständige Pipeline | Compiler, aber lesbare Quelle |
| Semantik | Custom-Elemente als Default | Native HTML-Elemente bevorzugt |
| Versionsstabilität | Breaking Changes bei Transforms | Stabile Template-Syntax |
JSX löst kein echtes Problem — es schafft künstliche durch Nicht-Standards-Konformität. Es priorisiert „JS-everywhere” über Web-Standards, was mittel- bis langfristig mehr Reibung erzeugt als es spart.
Architektur? Welche Architektur?
Wer aus klassischen Architekturmodellen kommt — MVC, MVVM, Template-Systeme — merkt schnell: React interessiert sich nicht für diese Konzepte. Es gibt kein definiertes View-Konzept, keine saubere Trennung von Zuständigkeiten, keine vorgegebene Struktur.
Stattdessen: „Mach halt Komponenten.”
Das klingt flexibel. In der Praxis bedeutet es: Jedes Projekt erfindet seine eigene Architektur. Team A macht es anders als Team B. Onboarding wird zum Archäologieprojekt. Wartbarkeit hängt komplett von der Disziplin der Entwickler ab, nicht vom Framework.
Schau dir ein React-Projekt an, das zwei Jahre alt ist und von drei verschiedenen Teams bearbeitet wurde. Du findest Container-Components neben Presentational Components neben Hooks-only-Patterns neben HOCs aus der Pre-Hooks-Ära. Manche Dateien nutzen Redux, andere Zustand, wieder andere lokalen State mit useReducer. CSS-Modules hier, styled-components dort, Tailwind im neuesten Feature. Das ist kein Edge Case — das ist der Normalzustand.
React sagt dazu: „Das ist nicht unser Problem. Wir sind nur die UI-Schicht.” Und genau das ist das Problem. Ein Framework, das sich weigert, Architekturentscheidungen zu treffen, delegiert diese Entscheidungen an jedes einzelne Team in jedem einzelnen Projekt. Das Ergebnis ist vorhersagbar: Inkonsistenz, Wildwuchs, technische Schulden.
Astro geht einen anderen Weg. Eine .astro-Datei hat eine klare Struktur:
---
// Logik: Datenabruf, Imports, Berechnungen
const data = await fetchData();
---
<!-- Template: Struktur und Markup -->
<div class="box">{data.title}</div>
<style>
/* Styling: Scoped per Default */
.box { padding: 1rem; }
</style>
Frontmatter für Logik, Template für Markup, Style-Block für CSS. Drei klar getrennte Bereiche in einer Datei. Jedes Astro-Projekt sieht gleich aus — nicht weil die Entwickler keine Freiheit haben, sondern weil das Framework sinnvolle Entscheidungen für sie getroffen hat. Das ist keine Einschränkung — das ist Architektur.
Hooks — mächtig, aber ein Minenfeld
Hooks werden oft als Reacts größte Innovation verkauft. Sie haben Klassenkomponenten tatsächlich abgelöst und State-Management vereinfacht. Das ist unbestritten. Aber sie lösen im Kern ein Problem, das React selbst erzeugt hat: Klassenbasierte Komponenten waren zu umständlich, also brauchte man einen neuen Mechanismus. Und dieser Mechanismus hat seine eigenen, nicht unerheblichen Probleme.
function SearchResults({ query }) {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setIsLoading(true);
fetchResults(query)
.then(data => {
if (!cancelled) {
setResults(data);
setIsLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err);
setIsLoading(false);
}
});
return () => { cancelled = true; };
}, [query]);
// ...
}
Das ist ein einfacher Datenabruf. Nicht komplex, nicht ungewöhnlich. Und trotzdem brauchst du: drei State-Variablen, einen Effect mit Cleanup-Funktion, ein cancelled-Flag gegen Race Conditions, und du musst aufpassen, dass query im Dependency-Array steht — sonst läuft der Effect nicht bei Änderungen, oder er läuft bei jedem Render.
Vergiss eine Dependency, und du hast einen Bug, der sich erst unter bestimmten Bedingungen zeigt. Füge eine falsche hinzu, und du bekommst eine Endlosschleife. Beides sind keine Anfängerfehler — es sind strukturelle Fallen des Modells. Die Zahlen bestätigen das: Rund 80-90 % aller React-Entwickler kämpfen mit fehlenden oder falschen Dependencies in useEffect — es ist die häufigste Bug-Kategorie in React-Anwendungen überhaupt.
Die kognitive Last geht noch weiter. Du musst permanent im Kopf behalten:
- Wann rendert eine Komponente neu? Jede State-Änderung, jede Prop-Änderung, jeder Context-Update triggert ein Re-Render. Nicht nur der betroffenen Komponente, sondern des gesamten Teilbaums darunter.
- Stale Closures. Funktionen in Effects oder Callbacks „sehen” den State zum Zeitpunkt ihrer Erstellung, nicht den aktuellen. Das führt zu Bugs, die aussehen wie Race Conditions, aber in Wirklichkeit Closure-Probleme sind.
- Referenzielle Stabilität. Objekte und Arrays, die bei jedem Render neu erstellt werden, triggern unnötige Re-Renders bei Child-Komponenten. Die Lösung:
useMemounduseCallbacküberall, was den Code aufbläht, ohne Mehrwert zu schaffen. - Hook-Regeln. Hooks dürfen nur auf Top-Level aufgerufen werden, nie in Bedingungen oder Schleifen. Das ist eine künstliche Einschränkung, die aus der internen Implementierung kommt (Hooks werden über einen Array-Index zugeordnet), nicht aus einer logischen Notwendigkeit.
Das sind keine Randprobleme. Das ist der Alltag in React-Projekten. Etwa 70 % der Entwickler nennen die Hook-Regeln als eine der größten Lernbarrieren. Und rund 40 % der React-Projekte haben nach sechs Monaten signifikante State-Schulden — einen Wildwuchs aus useState, useReducer und verteiltem Context, der kaum noch wartbar ist. Der Aufwand, diese Fallen zu umgehen, wächst mit der Komplexität der Anwendung.
In Svelte 5 sieht Reaktivität so aus:
<script>
let { query } = $props();
let results = $state([]);
let isLoading = $state(false);
let error = $state(null);
$effect(() => {
isLoading = true;
fetchResults(query)
.then(data => { results = data; isLoading = false; })
.catch(err => { error = err; isLoading = false; });
});
</script>
Kein Dependency-Array — der Compiler erkennt automatisch, dass der Effect von query abhängt. Kein cancelled-Flag — Svelte handhabt Cleanup. Keine referenzielle Instabilität — Svelte trackt Werte, nicht Referenzen. Kein useCallback, kein useMemo. Die Komplexität liegt im Compiler, nicht im Kopf des Entwicklers.
Das ist ein konzeptionell einfacheres Modell. Nicht weil es weniger kann, sondern weil es die richtige Abstraktionsebene wählt.
React ist kein vollständiges System
React ist nur die UI-Schicht. Für alles andere brauchst du zusätzliche Libraries: Routing, State Management, Datenfetching, SSR, Formulare, Validierung. Das führt zu einer Fragmentierung, die sich durch das gesamte Ökosystem zieht:
- Redux oder Zustand oder Jotai oder Recoil oder MobX oder Valtio?
- Next.js oder Remix oder Vite oder Gatsby?
- React Query oder SWR oder Fetch oder Apollo?
- React Router oder TanStack Router oder wouter?
- React Hook Form oder Formik oder eigenes?
- CSS Modules oder styled-components oder Emotion oder Tailwind oder vanilla-extract?
Jedes Projekt trifft diese Entscheidungen anders. Und jede Entscheidung zieht weitere nach sich. Wählst du Redux, brauchst du Redux Toolkit, Redux Thunk oder Saga, und Redux DevTools. Wählst du Next.js, bist du an Vercels Ökosystem gebunden. Wählst du styled-components, hast du eine Runtime-CSS-Lösung, die Performance kostet.
Das Ergebnis ist ein Dependency-Graph, der bei jedem nicht-trivialen Projekt zweistellig wird. Jede dieser Dependencies hat eigene Versionszyklen, eigene Breaking Changes, eigene Bugs. Die Wartung eines React-Projekts ist zu einem erheblichen Teil die Wartung seiner Dependencies.
Next.js und Remix haben dieses Problem teilweise entschärft, indem sie strukturelle Konventionen hinzufügen. Aber das bestätigt den Punkt: React allein reicht nicht. Es braucht ein Meta-Framework, das die fehlenden Entscheidungen nachholt. Und dann bist du nicht mehr bei „React”, sondern bei „Next.js” — einem opinionated Framework, das auf einer unopinionated Library aufsetzt. Die Ironie ist offensichtlich.
Frameworks wie Astro oder SvelteKit liefern diese Entscheidungen von Anfang an — Routing, SSR, Build-Pipeline und Datenhandling aus einer Hand. Nicht weil sie alles besser können, sondern weil ein integriertes System konsistenter ist als eine Sammlung loser Teile.
Für „normale” Websites — Marketing, Content, Blogs, Dokumentation, einfache Dashboards — ist React damit oft massiv überdimensioniert. Build-Chain, Dev-Server, Hydration, Routing-Library, State-Library, CSS-Lösung: Das ist ein Infrastrukturaufwand, der in keinem Verhältnis zum Ergebnis steht. Du baust eine Brücke, um einen Bach zu überqueren, der knietief ist.
Keine Konventionen — das ist keine Freiheit
React verkauft das Fehlen von Vorgaben als Feature. Keine vorgeschriebene Ordnerstruktur, kein Naming-Standard, keine Architekturvorgabe. „Du entscheidest selbst.” Das klingt nach Freiheit. In der Praxis ist es Orientierungslosigkeit.
Jedes React-Projekt erfindet seine eigene Struktur. Wo liegen die Hooks? In einem hooks/-Ordner? Neben der Komponente? In einem utils/-Ordner? Gibt es eine Service-Schicht oder lebt alles in Hooks? Was ist ein Container, was eine Komponente, was ein Layout? Sind Styles neben der Komponente oder in einem globalen Ordner? Wie heißen die Dateien — PascalCase, camelCase, kebab-case?
Diese Fragen werden in jedem Projekt, in jedem Team, bei jedem Onboarding neu verhandelt. Wer drei React-Projekte gesehen hat, hat drei verschiedene Architekturen gesehen. Wer ins nächste Team wechselt, fängt bei null an. Und die Diskussion darüber, wie man es „richtig” macht, frisst Zeit, die besser in Features investiert wäre.
Für große Organisationen mit eigenem Architecture Board und dokumentierten Standards ist das weniger ein Problem. Für den Rest der Branche — Mittelstand, Agenturen, Freelancer, kleine Teams — erzeugt es unnötige Reibung bei jedem Projektwechsel.
Astro hat Content Collections mit definiertem Schema. SvelteKit hat filesystem-based Routing — Datei anlegen, Route existiert. Beide treffen Entscheidungen, die dir Arbeit abnehmen, ohne dich einzuschränken. Der Unterschied ist subtil, aber entscheidend: Konventionen reduzieren Reibung. Ihre Abwesenheit erzeugt sie.
Wer argumentiert, Konventionen seien Einschränkung, verwechselt Struktur mit Kontrolle. Die besten Frameworks geben dir einen Rahmen, innerhalb dessen du dich frei bewegen kannst. React gibt dir eine leere Leinwand und sagt: „Viel Spaß beim Rahmen bauen.” Und dann wundert sich die Branche, warum jedes React-Projekt anders aussieht.
Das Ökosystem ist kein Vorteil — sondern ein Symptom
„React ist so stark wegen seines Ökosystems.” Dieser Satz fällt in jeder React-vs-X-Diskussion. Und er stimmt — aber aus dem falschen Grund.
Das große Ökosystem existiert, weil React selbst so wenig vorgibt. Jede fehlende Standardlösung erzeugt drei konkurrierende Libraries. Kein eingebautes Routing? Drei Router-Libraries. Kein State Management? Fünf State-Libraries. Keine Form-Lösung? Vier Form-Libraries. Das ist kein Feature — das ist ein strukturelles Defizit, das durch unbezahlte Community-Arbeit kompensiert wird.
Die Maintainer dieser Libraries arbeiten oft in ihrer Freizeit. Sie reagieren auf Reacts Breaking Changes, passen ihre APIs an, schreiben Migrationsguides. Wenn React Server Components einführt, müssen alle Library-Autoren nachziehen. Wenn React eine neue Hook-API hat, muss das Ökosystem folgen. Die Community trägt die Last von Reacts Designentscheidungen — und React profitiert davon, ohne die Kosten zu tragen.
Wer zum Vergleich ein Astro-Projekt aufsetzt, hat nach npm create astro ein funktionierendes Setup mit Routing, Content Collections, Build-Pipeline und optionalem SSR. Ohne eine einzige zusätzliche Entscheidung. Ohne fünf Libraries zu evaluieren. Ohne sich zu fragen, ob die gewählte State-Library in einem Jahr noch maintained wird.
Virtual DOM — eine Lösung, die zum Problem wurde
React wurde zu einer Zeit entwickelt, in der DOM-Updates tatsächlich teuer waren. 2013 waren Browser langsamer, DOM-Manipulation war ein Flaschenhals, und der Virtual DOM war eine elegante Lösung: Berechne die Unterschiede im Speicher, aktualisiere nur das Nötige im echten DOM.
2026 sieht die Situation fundamental anders aus. Browser-Engines sind massiv optimiert. DOM-Updates sind schnell. Und der Virtual DOM? Er ist immer noch da — als zusätzliche Schicht, die bei jedem State-Update einen kompletten Baum im Speicher neu erstellt, ihn mit dem vorherigen vergleicht, die Unterschiede berechnet, und dann die minimal nötigen DOM-Updates ausführt.
Das klingt effizient. Ist es aber nicht.
Svelte kompiliert Reaktivität zu präzisen DOM-Updates. Der Compiler weiß zur Build-Zeit, welcher DOM-Knoten von welchem State abhängt. Wenn sich count ändert, wird exakt der eine Textknoten aktualisiert, der count anzeigt. Nicht der gesamte Komponentenbaum. Kein Diffing, kein Virtual DOM, kein Overhead.
SolidJS geht noch einen Schritt weiter und zeigt, dass man sogar Reacts API-Design — JSX, Hooks-ähnliche Primitives — beibehalten und trotzdem ohne Virtual DOM arbeiten kann. Das Argument „Virtual DOM gehört zu React” ist also nicht einmal innerhalb von Reacts eigenem Paradigma zwingend. React hat sich entschieden, den Virtual DOM zu behalten. Nicht weil er nötig wäre, sondern weil er zu tief in der Architektur steckt, um ihn zu entfernen.
Performance-Optimierung als Dauerbaustelle
Reacts Performance-Story klingt in der Theorie überzeugend: „Schreib deklarativen Code, React kümmert sich um die Performance.” In der Praxis bedeutet sie ständiges Micro-Management.
// Ohne Optimierung: Wird bei JEDEM Render neu erstellt
const handleClick = () => doSomething(id);
const filteredItems = items.filter(item => item.active);
const config = { theme: 'dark', locale: 'de' };
// Mit Optimierung: Boilerplate, die nichts zur Logik beiträgt
const handleClick = useCallback(() => doSomething(id), [id]);
const filteredItems = useMemo(() => items.filter(item => item.active), [items]);
const config = useMemo(() => ({ theme: 'dark', locale: 'de' }), []);
Jede Funktion, jedes Objekt, jedes Array, das in einer Komponente erstellt wird, ist bei jedem Render eine neue Referenz. Das triggert Re-Renders in allen Child-Komponenten, die diese Werte als Props empfangen — selbst wenn sich der Wert nicht geändert hat. Die Lösung: useCallback und useMemo überall, React.memo um Komponenten wickeln, Keys strategisch setzen.
Das ist kein Optimierungsproblem — das ist ein Designproblem. Rund 60 % der Performance-Probleme in React-Apps kommen von Prop-Drilling, fehlendem Memoizing oder Context-Missbrauch. 53 % der Nutzer verlassen Seiten bei mehr als drei Sekunden Ladezeit — oft verursacht durch Re-Render-Kaskaden, die mit Tools wie „Why did you render?” erst sichtbar werden. React rendert standardmäßig zu viel und überlässt es dem Entwickler, das zu verhindern. Du schreibst nicht Code, der etwas tut — du schreibst Code, der verhindert, dass React zu viel tut. Das ist die Umkehrung dessen, was ein Framework leisten sollte.
Granulare, signalbasierte Systeme wie Svelte, Solid oder Vue Reactivity vermeiden diese ganze Problemklasse. Wenn sich ein Signal ändert, werden nur die Subscriber aktualisiert — keine Re-Renders des gesamten Komponentenbaums, keine Referenzvergleiche, keine Memoization-Wrapper.
In wachsenden React-Projekten wird Performance-Tuning zu einer permanenten Wartungsaufgabe. Profiling, React.memo-Platzierung, Callback-Stabilisierung, Suspense-Boundaries, Transition-APIs — das ist Arbeit, die andere Frameworks gar nicht erst erzeugen. Du optimierst nicht deine Anwendung. Du optimierst um Reacts Rendering-Modell herum.
Testing wird unnötig kompliziert
React macht Testing schwieriger als nötig. Nicht weil Testing generell schwer wäre, sondern weil Reacts Architektur es erzwingt.
Durch Hooks und den stark gekapselten Component-State ist es schwierig, Logik isoliert zu testen, ohne das gesamte UI-Framework drumherum aufzusetzen. Ein einfacher Unit-Test für eine Datenverarbeitungsfunktion? Kein Problem. Aber sobald diese Logik in einem Custom Hook lebt, brauchst du:
renderHookaus React Testing Library- Einen Wrapper mit allen nötigen Providern (Router, Store, Theme, Auth)
- Mocking von Child-Komponenten, die der Hook indirekt beeinflusst
act()Wrapper für State-Updates, die asynchron passieren
// Was du testen willst: "Filtert Items nach Status"
// Was du dafür brauchst:
const wrapper = ({ children }) => (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router}>
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
</RouterProvider>
</QueryClientProvider>
);
const { result } = renderHook(() => useFilteredItems(), { wrapper });
act(() => result.current.setFilter('active'));
expect(result.current.items).toHaveLength(3);
Drei Provider-Wrapper, um eine Filterfunktion zu testen. Das ist kein Testing — das ist Infrastruktur-Setup.
Snapshot-Tests — einst als Reacts Antwort auf Regressionstests beworben — sind in der Praxis fast wertlos. Die Diffs werden schnell so lang, dass niemand sie liest. Jede Styling-Änderung, jede neue Prop triggert einen Snapshot-Fehler. Teams enden damit, Snapshots blind zu aktualisieren, was den gesamten Zweck untergräbt.
Wenn Businesslogik in Services oder Stores lebt statt in Hooks — wie es in vielen Nicht-React-Stacks üblich ist — wird Testing einfacher und stabiler. Die Logik hat keine Abhängigkeit zum Rendering-Framework und lässt sich mit simplen Unit-Tests prüfen. Kein renderHook, kein Provider-Wrapper, kein act().
Barrierefreiheit bleibt auf der Strecke
React abstrahiert den DOM, aber nimmt dir Barrierefreiheit nicht ab. Im Gegenteil: Die Komponentenphilosophie lädt aktiv dazu ein, native HTML-Elemente durch Custom-Implementierungen zu ersetzen.
Jedes React-Projekt hat irgendwo ein <div onClick>, das ein Button sein sollte. Ein Custom-Dropdown, das kein Keyboard-Event handhabt. Ein Modal, das den Fokus nicht fängt. Ein Tab-System ohne role="tablist" und aria-selected. Das passiert nicht aus Bosheit, sondern weil Reacts Architektur es nahelegt: „Bau deine eigenen Komponenten.” Und wenn du eigene Komponenten baust, musst du all das selbst implementieren, was native Elemente gratis mitbringen.
Ein <button> hat von Haus aus: Fokusbarkeit, Keyboard-Events (Enter und Space), ARIA-Role, Touch-Target-Sizing, visuelle Feedback-States. Ein <div onClick> hat: nichts davon. Du musst tabIndex, role, onKeyDown, aria-pressed manuell hinzufügen. Und vergisst garantiert mindestens eines davon.
Das ist kein React-spezifisches Problem, aber React fördert es stärker als andere Frameworks. Weil alles eine Komponente ist, weil JSX Custom-Tags normalisiert, weil die Community Component Libraries baut statt native Elemente zu nutzen. Headless UI und Radix haben das Problem erkannt und liefern barrierefreie Primitives — aber dass man eine Library braucht, um <dialog> als Komponente nachzubauen, sagt eigentlich alles.
Frameworks, die stärker auf native HTML-Elemente und Progressive Enhancement setzen, laufen seltener in diese Falle. <dialog>, <details>, popover — die Plattform liefert barrierefreie Interaktionsmuster, die kein Custom-Code nachbauen muss. Und die besser funktionieren als jede React-Komponente, weil der Browser sie seit Jahren optimiert.
Starker Lock-in durch spezifische Patterns
React-Projekte sind typischerweise eng an spezifische Patterns gekoppelt: Hooks, Context, JSX-Transformation, bestimmte Routing- und State-Lösungen. Ein Wechsel zu einem anderen Stack ist selten inkrementell möglich. Du kannst nicht „ein bisschen weniger React” machen. Entweder du bist im React-Ökosystem, oder du bist es nicht.
Das liegt an der Tiefe der Integration. JSX durchzieht jede Datei. Hooks koppeln Logik an Reacts Rendering-Zyklus. Context verteilt State über den Komponentenbaum. React Router strukturiert die gesamte Navigation. Jede dieser Entscheidungen ist für sich reversibel — aber zusammen erzeugen sie einen Lock-in, der Migrations-Projekte zu Neuentwicklungen macht.
HTML-first-Stacks wie Astro mit Islands, htmx oder klassische MPAs sind näher an Web-Standards und damit leichter austauschbar. Eine Astro-Seite ist im Kern HTML mit optionaler Interaktivität. Eine React-App ist JavaScript, das HTML generiert. Dieser Unterschied bestimmt, wie leicht du dich in Zukunft umorientieren kannst. Und in einer Branche, in der sich Frameworks alle paar Jahre ablösen, ist Exit-Fähigkeit kein Luxus — sie ist Risikomanagement.
Server Components — steigende Komplexität statt Vereinfachung
React versucht sich gerade neu zu erfinden: Server Components, Streaming, hybride Rendering-Modelle. Das Versprechen ist weniger Client-JavaScript und bessere Performance. Die Realität ist eine neue Schicht Komplexität, die auf die bestehenden Schichten aufgesetzt wird.
- Server vs. Client: Welche Komponente läuft wo? Du musst es bei jeder Komponente bewusst entscheiden und mit
"use client"markieren. Vergisst du es, bekommst du kryptische Fehlermeldungen. Markierst du zu viel, verlierst du den Vorteil. - Zwei Rendering-Modelle: Server Components können keine Hooks nutzen. Client Components können keine async/await nutzen. Du brauchst zwei mentale Modelle und musst wissen, welches wann gilt.
- Neue Fehlerklassen: Serialisierungsprobleme zwischen Server und Client. Event Handlers, die auf dem Server nicht existieren. State, der nicht zwischen Server- und Client-Phasen übertragen werden kann.
- Debugging wird zum Rätsel: Welcher Code lief auf dem Server? Welcher auf dem Client? Wo ist der Fehler aufgetreten? Die Antwort ist oft: auf beiden, aus unterschiedlichen Gründen.
Das ist der Versuch, Probleme zu lösen, die andere Frameworks gar nicht erst haben. Astro rendert standardmäßig alles auf dem Server und liefert statisches HTML aus. JavaScript wird nur dort geladen, wo es tatsächlich gebraucht wird — über Islands, die explizit als interaktiv markiert werden. Kein mentales Modell von Server vs. Client, keine Directives, keine impliziten Grenzen, keine Serialisierungsprobleme.
React holt 2026 nach, was Astro seit seiner Gründung kann. Nur mit deutlich mehr Komplexität. Server Components lösen echte Probleme — aber sie lösen sie auf eine Art, die neue Probleme erzeugt. Für viele Teams bedeutet das: noch mehr Pattern-Wissen, noch mehr Spezialfälle, noch steilere Lernkurve.
JavaScript-first statt Web-first
Reacts Denken ist fundamental JavaScript-zentriert. Eine React-Anwendung ist eine JavaScript-Applikation, die zufällig im Browser läuft. HTML ist ein Rendering-Target, kein Ausgangspunkt. Das prägt die gesamte Architektur — und ihre Schwächen.
Das führt zu hoher JavaScript-Payload: Typische React-Apps laden 200-500 KB JavaScript, bevor der Nutzer irgendetwas sieht. Initial-Load-Performance leidet, besonders auf mobilen Geräten mit langsamer Verbindung. Time to Interactive liegt regelmäßig bei mehreren Sekunden.
Semantik wird vernachlässigt: Statt <nav>, <article>, <aside> gibt es <div> mit className. Progressive Enhancement? Existiert nicht — ohne JavaScript keine Anwendung. noscript-Fallbacks? Werden belächelt. Die Idee, dass eine Website auch ohne JavaScript grundlegend funktionieren sollte, ist im React-Universum ein Fremdwort.
Frameworks, die HTML und Server-Rendering ins Zentrum stellen, belohnen ein anderes Denken: Was braucht der Nutzer wirklich? Wie viel JavaScript ist tatsächlich nötig? Die Antwort ist fast immer: weniger, als React standardmäßig ausliefert. Deutlich weniger.
Die Komplexitätskurve wächst mit dem Projekt
Am Anfang wirkt React schlank — „nur eine Library”. npx create-react-app, ein paar Komponenten, fertig. Die Einstiegshürde ist niedrig, der erste Prototyp steht schnell.
Aber dann wächst das Projekt. Und mit ihm die Komplexität — nicht linear, sondern exponentiell.
Nach drei Monaten hast du: eine State-Lösung, eine Form-Library, eine Query-Library, Routing, Auth, eine CSS-Strategie, ein Design-System, eigene Konventionen, die nirgendwo dokumentiert sind.
Nach einem Jahr hast du: ein Dependency-Upgrade, das drei Libraries gleichzeitig betrifft. Eine Migration von React Router v5 auf v6, die jeden Route-Handler anfasst. Ein React-18-Upgrade, das Concurrent-Mode-Probleme aufdeckt, die vorher unsichtbar waren. Und einen neuen Entwickler, der zwei Wochen braucht, um die projektspezifische Architektur zu verstehen.
Nach zwei Jahren hast du: technische Schulden aus drei verschiedenen Architektur-Paradigmen (Klassen, Hooks, Server Components), die nebeneinander existieren. Libraries, die nicht mehr maintained werden. Und die Diskussion, ob man nicht besser von vorne anfängt.
Ein integriertes Framework kann anfangs schwerer wirken, hat aber eine flachere Komplexitätskurve über Jahre. Wer nach zwei Jahren ein React-Projekt übernimmt, muss erst die projektspezifische Architektur verstehen, die Meta-Frameworks-Wahl nachvollziehen, die State-Lösung lernen und die CSS-Strategie begreifen. Wer ein SvelteKit- oder Astro-Projekt übernimmt, kennt die Architektur bereits — weil das Framework sie vorgibt.
Warum React sich trotzdem durchgesetzt hat
Nicht weil es die beste Lösung war. Sondern weil die Umstände gestimmt haben:
- Es war 2013 eines der ersten komponentenbasierten Frameworks, das produktionsreif war
- Facebook stand dahinter — und Facebook war 2013 das Technologie-Unternehmen schlechthin
- Die Community wuchs schnell, was ein sich selbst verstärkendes Ökosystem erzeugte: mehr Libraries, mehr Tutorials, mehr Jobs, mehr Entwickler, mehr Libraries
- Tooling folgte der Masse: Create React App, React DevTools, TypeScript-Support
- Der Jobmarkt zementierte die Dominanz: Unternehmen suchten React-Entwickler, also lernten Entwickler React, also suchten Unternehmen React-Entwickler
- Wechselkosten stiegen mit jeder Zeile React-Code, jedem Custom Hook, jeder projektspezifischen Architektur
Das ist kein technisches Argument. Das ist Pfadabhängigkeit. Ein sich selbst verstärkender Kreislauf, in dem die Verbreitung einer Technologie ihre weitere Verbreitung begünstigt — unabhängig von ihrer technischen Qualität.
Das soll Reacts historischen Beitrag nicht schmälern. Komponentenbasiertes Denken, deklarative UIs, die Idee von UI-as-a-Function-of-State — React hat diese Konzepte populär gemacht, auch wenn es sie nicht erfunden hat. Die Frage ist nicht, ob React wichtig war. Sondern ob es heute noch die richtige Wahl ist.
Und genau hier liegt das vielleicht größte Problem: React als Default verhindert Innovation. Weil „alle React können” und der Jobmarkt es verlangt, evaluieren viele Teams gar nicht mehr ernsthaft, was sie eigentlich brauchen. Architekturentscheidungen werden aus alten Projekten kopiert, es gibt wenig Experimentierfreude, und technische Schulden entstehen durch Fehlpassung von Problem und Werkzeug.
Der organisatorische Lock-in — Hiring, bestehende Codebases, Schulungen, Tooling — verdrängt bessere Optionen. Das ist keine technische Entscheidung mehr. Das ist Trägheit, die sich als Pragmatismus tarnt.
Die Fehlerstatistik spricht für sich
Man kann über Designphilosophien diskutieren. Über Zahlen weniger. Reacts häufigste Fehlerklassen sind nicht zufällig, sondern systemisch — und moderne Alternativen haben sie weitgehend eliminiert:
| Fehlerklasse | Häufigkeit | Warum veraltet? |
|---|---|---|
| useEffect Dependencies | ~90 % der Entwickler betroffen | Signals (Solid, Svelte, Angular) brauchen keine Arrays — Reaktivität ist feingranular |
| Re-Render-Kaskaden | ~60 % der Performance-Issues | Svelte kompiliert zu präzisen DOM-Updates, kein Diffing nötig |
| Hook-Regeln | ~70 % als Lernbarriere genannt | Compiler-Frameworks eliminieren die Notwendigkeit komplett |
| State-Schulden nach 6 Monaten | ~40 % der Projekte | Integrierte Stores und Signals verhindern Wildwuchs |
Besonders bezeichnend: React braucht Error Boundaries als separaten Mechanismus, weil ein Fehler in einer Komponente den gesamten Component-Tree crashen kann. In Svelte, Solid oder Qwik passiert das nicht — Fehler bleiben lokal, weil Reaktivität granular ist, nicht baumweit. Wenn ein Framework einen eigenen Crash-Handler braucht, um sich vor sich selbst zu schützen, ist das kein Feature. Das ist ein Eingeständnis.
useEffect allein ist ein rotes Flag — ein Konzept von 2018, das 2026 immer noch die Bug-Nummer-eins in React-Anwendungen ist. Das ist kein „Charakteristikum”. Das ist ein Designmangel, der React gegenüber compiler-basierten Lösungen altbacken wirken lässt.
Was ich stattdessen einsetze
Ich arbeite seit Jahren mit einem Stack, der bewusst auf React verzichtet. Nicht als Experiment, sondern in Produktion. Für Kundenprojekte, für meine eigenen Produkte, für alles.
Astro als Metaframework. Statisch per Default, SSR wo nötig, Island Architecture für gezielte Interaktivität. Kein JavaScript wird ausgeliefert, das nicht explizit angefordert wird. Content Collections für strukturierte Inhalte mit Schema-Validierung. Filesystem-based Routing. Scoped Styles per Default. Eine klare, wartbare Architektur, die bei jedem Projekt gleich funktioniert.
Svelte für interaktive Inseln. Dort, wo echte Client-Interaktivität gebraucht wird — Formulare mit Validierung, dynamische Filter, interaktive Visualisierungen — nutze ich Svelte-Komponenten als Islands in Astro. Svelte kompiliert zu minimalem JavaScript, die Reaktivität ist in die Sprache eingebaut, Templates sind echtes HTML. Der Compiler optimiert, nicht der Entwickler.
Vanilla JavaScript und Web Standards für den Rest. <dialog> für Modals, <details> für Akkordeons, popover für Tooltips und Dropdowns, :has() für Parent-Selektoren. Viele Interaktionsmuster, für die man früher ein Framework brauchte, gehen heute nativ. Das ist keine Nostalgie, sondern technischer Fortschritt: weniger Abhängigkeiten, weniger Bundle-Size, bessere Performance, volle Barrierefreiheit out of the box.
Zustand oder Jotai als State-Layer — aber in der Vanilla-Variante, ohne React-Hooks. Beide haben einen framework-agnostischen Core, den man mit jedem Rendering-Ansatz kombinieren kann. State-Management ohne Kopplung an ein UI-Framework. So, wie es sein sollte.
Dieser Stack ist keine exotische Nische. Astro wächst, Svelte gewinnt an Verbreitung, Web Standards werden mächtiger. Die Richtung ist klar — weg von JavaScript-first, hin zu HTML-first mit gezielter Interaktivität.
Warum die Komponentenbibliotheken trotzdem bei React bleiben
Wenn React so viele Probleme hat — warum bauen MUI, Ant Design, Chakra UI und Dutzende andere Libraries weiterhin exklusiv auf React auf? Die Antwort hat wenig mit technischer Überlegenheit zu tun und alles mit Markt-, Community- und Pfadabhängigkeit.
Der Netzwerkeffekt
React hat über 40 % Marktanteil bei Frontend-Frameworks. Wer eine Komponentenbibliothek baut und damit Geld verdienen oder eine große Community erreichen will, baut sie für React. 90 % der Frontend-Jobs fordern React-Kenntnisse. Svelte, Solid und Co. haben kleinere Teams, kleinere Budgets, kleinere Nutzerbasis. Die Lib-Landschaft folgt dem Geld, nicht den Standards.
Das erzeugt einen sich selbst verstärkenden Kreislauf: Mehr React-Libraries machen React attraktiver, was mehr Entwickler anzieht, was mehr Libraries hervorbringt. Technische Qualität spielt in diesem Kreislauf eine untergeordnete Rolle.
JSX als vermeintlicher Vorteil
Lib-Autoren argumentieren gern: „Alles ist JavaScript!” JSX erlaubt es, Komponenten als Funktionen zu schreiben, Props als Parameter zu übergeben, Composition durch verschachtelte Aufrufe auszudrücken. Das klingt elegant — bis man sich die Realität anschaut:
// React: Boilerplate für einen Button
const Button = ({ onClick, children, variant = 'primary' }) => (
<button
className={`btn btn--${variant}`}
onClick={onClick}
>
{children}
</button>
);
<!-- Svelte: Gleiche Funktionalität, weniger Zeremonien -->
<script>
let { onclick, children, variant = 'primary' } = $props();
</script>
<button class="btn btn--{variant}" {onclick}>
{@render children()}
</button>
Die React-Version ist nicht einfacher zu integrieren. Sie ist boilerplate-lastiger. Und sie erzeugt eine Abhängigkeit an Reacts gesamtes Rendering-Modell für etwas, das am Ende ein <button> ist.
Die 10-Zeilen-vs-200-Zeilen-Frage
Der absurdeste Vergleich zeigt sich bei Modals. Ein React-Modal braucht: Portal, Overlay-Component, Focus-Trap-Logic, Escape-Handler, Click-Outside-Handler, ARIA-Attributes, Animation-State, Scroll-Lock. 200 Zeilen, mindestens. Plus eine oder zwei Dependencies.
Nativ in 2026:
<dialog id="myDialog">
<p>Inhalt</p>
<button onclick="this.closest('dialog').close()">Schließen</button>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Öffnen</button>
Zehn Zeilen. Fokus-Management eingebaut. Escape-Handler eingebaut. Backdrop eingebaut. Barrierefreiheit eingebaut. Kein JavaScript-Framework nötig.
Dass trotzdem Dutzende React-Modal-Libraries existieren, zeigt nicht die Stärke des Ökosystems — es zeigt, dass React-Entwickler Probleme lösen, die sie ohne React nicht hätten.
Lock-in durch React-spezifische Patterns
Viele Libraries nutzen React-Konzepte, die ohne React keinen Sinn ergeben: useContext für Theme-Provider, useState für internen State, React.memo für Performance-Optimierung, forwardRef für Ref-Weiterleitung. Das macht framework-agnostische Versionen extrem aufwändig. Eine „Vanilla JS”-Version derselben Library müsste eigenes State-Management, eigenes Event-System und eigenes DOM-Diffing implementieren — mehr Code, weniger intuitiv, kaum Wartbarkeit.
Das Ergebnis: Libraries, die technisch an React gebunden sind, nicht weil React die beste Plattform dafür wäre, sondern weil sie Reacts spezifische Abstraktionen so tief nutzen, dass eine Portierung einem Rewrite gleichkäme.
Enterprise-Trägheit
Große Unternehmen — FAANG, Banken, SaaS-Unternehmen — haben Millionen Zeilen React-Code. Sie wollen ein Design-System mit MUI, konsistente Komponenten über hundert Teams, Migration ohne Rewrite. Webstandards? Zu riskant für Legacy-Codebases, zu unbekannt für Enterprise-Architekten, zu wenig Tooling-Support in der bestehenden Infrastruktur.
Das ist kein technisches Argument. Das ist organisatorische Trägheit. Und sie ist mächtig genug, um eine gesamte Branche an einer Technologie festzuhalten, die ihre besten Tage hinter sich hat.
Die Gegenbewegung existiert
Framework-agnostische Alternativen gewinnen an Boden:
- Shoelace und Web Components: Native Custom Elements, framework-neutral, funktionieren überall
- Headless UI: React, Vue und Svelte gleichermaßen bedient
- Shadcn/UI: Kopierbare Tailwind-Komponenten statt Runtime-Library — keine Dependency, keine Updates, kein Lock-in
- cva (class-variance-authority): Pure TypeScript-Typen für Variants, framework-unabhängig
- Open UI: Standardisierung von UI-Patterns auf Browser-Ebene
- Webspire: Kuratierte Registry für framework-agnostische Snippets, Patterns und Komponenten — direkt nutzbar, ohne Dependency
Der Trend ist klar: Weg von monolithischen React-Libraries, hin zu kopierbaren, standardbasierten, framework-agnostischen Lösungen. Für neue Projekte macht Shoelace plus Tailwind mehr Sinn als eine React-Komponentenbibliothek. Für Enterprise bleibt React vorerst der Default. Aber „vorerst” hat ein Ablaufdatum.
Die unbequeme Frage
Wenn React heute neu vorgestellt würde — mit JSX, Hooks, Virtual DOM, dem Fehlen von Routing, State Management und Konventionen, und der Notwendigkeit, für jede Standardfunktion eine externe Library zu wählen — würde es sich gegen Svelte, Astro oder SolidJS durchsetzen?
Die ehrliche Antwort ist: nein.
React hat Frontend-Entwicklung vorangebracht, Komponenten-Denken populär gemacht und ein Ökosystem ermöglicht, von dem die gesamte Branche profitiert hat. Das ist unbestritten. Aber Dankbarkeit für historische Verdienste ist kein Grund, an Designentscheidungen festzuhalten, die heute bessere Alternativen haben.
Nicht jeder Punkt in diesem Artikel trifft auf jedes React-Projekt zu. Wer eine bestehende React-Codebase hat und damit produktiv arbeitet, hat keinen Grund zur Panik. Migration um der Migration willen ist Verschwendung. Aber wer ein neues Projekt startet und nicht an bestehende React-Codebases gebunden ist, sollte sich ernsthaft fragen: Muss es React sein? Oder ist es nur das, was man kennt?
Die Antwort auf diese Frage ist wichtiger als jedes technische Argument in diesem Artikel.
Quellen
- React useEffect: Häufige Fehler und wie man sie vermeidet — Analyse der häufigsten useEffect-Bugs
- React JS Performance Guide — Sentry-Daten zu Performance-Problemen in React-Apps
- React JS: Top Challenges and Common Issues — Entwicklerumfrage zu den größten React-Herausforderungen
- Häufig auftretende React JS-Probleme und Lösungsansätze — Re-Render-Analyse und Nutzerverhaltens-Statistiken
- Error Boundaries — React Legacy Docs — Reacts eigener Crash-Handler-Mechanismus
- 6 Common React Anti-Patterns — Strukturelle Probleme in React-Codebases
- Vergleich der Frontend Frameworks 2025 — Framework-Vergleich mit Performance-Benchmarks
- JavaScript hat das Web zerstört (und es Fortschritt genannt) — Kritik am JavaScript-first-Ansatz im Web
- Komponentenbibliotheken für die Web-Entwicklung — Überblick und Framework-Abhängigkeit
- React im modernen Web-Design — Marktdominanz und Netzwerkeffekte