Reagieren ohne Metaframework: Die versteckten Kosten eines Python-Backends
Warum React plus Vite auf einem nicht-JavaScript-Backend die Debatte um Next.js überflüssig macht, welche Framework-Funktionen man letztendlich neu implementieren muss und wie man die Übergänge handhabt.
Die meisten Debatten über React-Frameworks gehen davon aus, dass es nur zwei Optionen gibt: entweder bei Next.js bleiben oder zu dem übernehmen, was derzeit als Nachfolger beworben wird – sei es Astro, Remix oder TanStack Start. Ein großer Teil der realen React-Anwendungen wählt keinen dieser Wege. Sie rendern eine Single-Page-Client-Anwendung mit Vite und kommunizieren mit einem in Python, Ruby oder PHP geschriebenen Backend, das bereits die Datenbank, die Geschäftsregeln sowie die API bereitstellt. Dieser Artikel betrachtet ehrlich diese dritte Option: welche Kritikpunkte an Meta-Frameworks einfach nicht zutreffen, welche Verantwortlichkeiten stillschweigend in Ihren eigenen Code übergehen und wie man das entstehende, selbst entwickelte Framework wartbar hält.
Ein konkretes Beispiel für die dritte Option
Stellen Sie sich ein Gesundheitssystem vor, das die Verstärkung von Lebensmitteln überwacht. Die Frontend-Entwicklung erfolgt mit React und TypeScript, wobei Ant Design als Komponentenbibliothek verwendet wird. Auf der Serverseite kommt Frappe zum Einsatz, das Python-Framework, das ERPNext antreibt. Frappe definiert Datenmodelle als „Doctypes“, verwaltet die Datenbank und stellt ausgewählte Python-Methoden als API-Endpunkte zur Verfügung. In der Produktion gibt es keinen Node.js-Runtime, keinen App Router und keine Serverkomponenten, die entscheiden, welche Teile der Benutzeroberfläche auf die Datenbank zugreifen dürfen. React zeichnet den Bildschirm; Frappe erledigt alles Weitere.
Für ein Team in dieser Situation sind viele der lautesten Einwände gegen Next.js irrelevant. Eine Bindung an Vercel spielt keine Rolle, wenn dort nichts deployt wird. Langsame Hot-Module-Replacement-Prozesse unter dem App Router sind kein Problem, da Vite den Entwicklungsserver betreibt und so schnell neu lädt, dass niemand darauf achten muss.
Die gleiche Architektur gilt immer dann, wenn React mit Django, Rails, Laravel oder jedem anderen Stack verbunden wird, der bereits seine eigene Server-Seitengestaltung hat. Die verbleibenden Kritiken an Meta-Frameworks tauchen jedoch weiterhin auf – sie kommen nur aus einer anderen Richtung.
Man baut weiterhin ein Framework, Schritt für Schritt
Durch das Überspringen eines Meta-Frameworks werden die von ihm gelösten Probleme nicht beseitigt. Sie gelangen stattdessen in Ihre Codebasis, wo Sie sie selbst lösen müssen – meist schrittweise und ohne einen Namen für das, was Sie da entwickeln. Drei Beispiele aus der Fortification-App zeigen dieses Muster.
Schemabasierte Formulare anstelle von Framework-Konventionen
Das Team hat ein DynamicForm-Komponente entwickelt, die Formulare direkt aus den doctype-JSON-Definitionen abruft, die vom Backend bereitgestellt werden. Es liest das Schema, ermittelt, welche Felder vorhanden sind und welchen Typ jedes davon hat, markiert erforderliche Felder, kümmert sich um Felder, deren Sichtbarkeit oder Wert von einem anderen Feld abhängt, und rendernt schließlich das vollständige Formular. Um einen neuen Doctype hinzuzufügen, ist es nicht mehr notwendig, das Formular manuell zu erstellen.
Das ist Konvention statt Konfiguration – das zentrale Versprechen jedes Meta-Frameworks, hier intern umgesetzt. Es funktionierte auch nicht auf den ersten Versuch: Das Komponente musste dreimal komplett umgeschrieben werden, bevor es stabil war. Ein Framework hätte diese Iterationen im Namen des Benutzers bei Tausenden von Nutzern übernommen.
Paginiertes Laden ohne Datenlayer
Ein Bericht, der auflistet, dass die Zeilen im Laufe des Scrollens des Benutzers in Gruppen geladen werden müssen. Ohne getServerSideProps, einen Route-Loader oder irgendeine andere vom Framework vorgesehene Methode zum Abrufen von Daten, musste die Lösung aus verschiedenen Komponenten zusammengestellt werden: ein IntersectionObserver, der einen Markierungspunkt am unteren Ende der Liste überwacht, ein manuell verfolgter Seitenzeiger sowie ein Ladezustand, dessen Verhalten von Grund auf entworfen werden musste. Sollte am unteren Ende ein Ladeindikator angezeigt werden oder sollen einfach neue Zeilen schrittweise sichtbar werden? Da es keine Vorgaben des Frameworks gab, traf ein einzelner Entwickler diese Produktentscheidung mitten in einem gewöhnlichen Arbeitstag.
In einer React-plus-Vite-Umgebung kann eine Bibliothek wie TanStack Query einen Großteil davon bereitstellen (endlose Abfragen, Cursor, Ladeindikatoren), und es lohnt sich, diese vor dem Eigenentwickeln der Logik zu prüfen. Der grundlegende Punkt bleibt unverändert: Jemand muss sich für die Datenlage entscheiden und Verantwortung dafür übernehmen.
Eine Anpassungsschicht für inkonsistente Backend-Daten
Dropdowns für etwa ein Dutzend Dokumenttypen mussten menschenlesbare Beschriftungen anzeigen, doch jeder Dokumenttyp benannte sein Beschriftungsfeld unterschiedlich. Einige verwendeten label, andere title, und ein veralteter Dokumenttyp nutzte display_name. Eine kleine mapOptions-Funktion normalisiert alle diese Felder in ein einheitliches Format, sodass jedes Dropdown-Komponente die Inkonsistenzen ignorieren kann.
Niemand würde das als Architektur bezeichnen, doch es handelt sich genau um die Art von Normalisierung, die sonst eine Framework-Vereinbarung durchsetzen würde. Jedes Team entwickelt letztendlich die Grundstruktur, die ein Meta-Framework bereitgestellt hätte. Die einzige Wahl besteht darin, ob man dafür die Vorgaben eines Frameworks akzeptiert oder eigene Entwicklungszeit investiert.
Wenn das selbst entwickelte Framework zum Problem wird
Der eigentliche Kostenfaktor zeigt sich später. Nach einigen Jahren wies SonarQube das Komponenten DynamicForm auf eine übermäßige kognitive Komplexität hin. Es handelte sich dabei weder um einen Fehler in der Bibliothek noch um eine Versionsunverträglichkeit. Es war einfach das normale Wachstum einer Komponente: Eine Datei wurde stillschweigend zur Routing-Schicht, zur Formularschicht und zur Validierungs-Schicht der Anwendung zugleich.
Durch das Refactoring wurde sichtbar, wie verwickelt der Code geworden war. Durch die Extraktion der Validierungslogik in eine eigene Funktion kamen vier unverwandte Codepfade zum Vorschein, die von Nebenwirkungen abhängig waren, die innerhalb des Komponenten versteckt waren. Das ist die „Steuer“, die man selten auf der Rechnung sieht, wenn man auf ein Meta-Framework verzichtet. Man entwickelt die Infrastruktur – und wird zugleich ihre einzige Dokumentation. Es gibt keinen beliebten Issue-Tracker, in dem jemand anderes bereits vor derselben Herausforderung stand und eine Lösung veröffentlicht hat.
Die Lösung folgte einer bekannten Aufteilung:
- Die Bearbeitung von Feldabhängigkeiten in einen speziellen, benutzerdefinierten Hook verlagern.
- Die Validierung in ein separates Modul legen, das nichts über die Darstellung des Formulars weiß.
- Die Komponente selbst ausschließlich für die Darstellung verantwortlich lassen.
Nach der Aufteilung sank die Komplexitätsbewertung und das Komponenten wurde leichter zu lesen. Die übergeordnete Lektion ist, dass die Trennung der Aufgaben, die ein Meta-Framework durch Konvention vorschreibt, in einer benutzerdefinierten Architektur jedes Mal durch Disziplin durchgesetzt werden muss. Kein Linter warnt einen davor, dass man die Datenschicht eines Frameworks falsch neu aufbaut. Nützliche Gewohnheiten sind regelmäßige Komplexitätsberichte, Code-Reviews, bei denen gefragt wird: „Wird diese Komponente zur Infrastruktur?“, sowie kurze interne Dokumentationen für jede Utility, von der mehr als nur wenige Screens abhängen.
Bugs, die in der Nahtstelle zwischen zwei Ökosystemen existieren
Ein Framework wie Next.js beherrscht sowohl den Server als auch den Client, wodurch die Grenze zwischen ihnen als ein einheitliches System konzipiert ist. Wenn React auf einem Python-Backend läuft, muss diese Grenze selbst definiert werden, was zu einer eigenen Kategorie von Fehlern führt.
Ein Beispiel: Ein MySQL-Fehler 1292, dessen Ursache erst nach fast einem Tag ermittelt werden konnte. Der Fehler weist auf einen ungültigen oder falsch formatierten Datums- oder Zeitwert hin. Das React-Formular sendete die Daten in einem Format, während Frappes ORM ein anderes erwartete. Weder das Frontend noch das Backend lag allein betrachtet falsch; das Problem entstand durch eine Inkompatibilität zwischen zwei Systemen, die nie gemeinsam entwickelt wurden und nur durch Monate zuvor erfundene sowie teilweise vergessene Konventionen miteinander verbunden waren.
Ein weiteres Beispiel: Ein Validierer für die Passwortstärke, der unerwartet innerhalb eines Backend-Hooks namens after_insert ausgelöst wurde – durch eine von dem Formular gesendete Datenstruktur, die niemand getestet hatte. Kein Framework-Changelog beschreibt diese Wechselwirkung, da kein Meta-Framework von Frappe weiß. Um das Problem zu beheben, muss man sowohl den eigenen Code als auch die Quellcode des Backend-Frameworks durchgehen, bis beide Seiten übereinstimmen.
Einige praktische Schutzmaßnahmen gegen Fehler bei Schnittstellen:
- Serialisieren Sie Datums- und Zeitangaben an genau einem Ort auf der Client-Seite im von der Backend-Schnittstelle geforderten Format – und nicht pro Formular.
- Betrachten Sie den Anfrage-Payload als Vertragsgrundlage: Definieren Sie seine Struktur in TypeScript und validieren Sie ihn gegebenenfalls an beiden Endpunkten.
- Schreiben Sie Integrationstests, die echte Formulardaten an das tatsächliche Backend senden, einschließlich ungewöhnlicher Datensätze, da Serverseitige Mechanismen auf Daten reagieren können, die man nicht erwartet.
- Protokollieren Sie den Roh-Payload bei Fehlern bei der Backend-Validierung, damit Unstimmigkeiten sofort sichtbar werden.
Falls Ihr Backend ebenfalls JavaScript ist, beseitigt ein gemeinsames Validierungsschema einen großen Teil dieser Hindernisse; siehe das Teilen eines einzigen Zod-Schemas zwischen einem React-Frontend und einem Node-Backend. Bei einem Python-Backend können Sie den Code zwar nicht teilen, aber den Vertrag weiterhin gemeinsam nutzen.
Richtige Entscheidungsfindung
Die Debatte um „welches Meta-Framework“ setzt voraus, dass das Backend verhandelbar ist. Für viele Teams gilt das jedoch nicht. Wenn eine bereits vorhandene Django-, Rails-, Laravel- oder Frappe-Anwendung den Server besitzt, stellt React plus Vite kein Kompromiss dar – es handelt sich dabei um die natürliche Architektur. Die eigentliche Frage ist vielmehr, wie viel von der Arbeit eines Frameworks Sie bereit sind, selbst auf einer bereits vorhandenen Backend-Plattform zu entwickeln und zu warten.
Dafür können Sie den Backend-Service auswählen, der am besten zum Anwendungsgebiet passt. Für ein System, das auf Doctypes und Python-Hooks basiert, ist diese Freiheit von großem Wert. Der Preis dafür ist, alle damit verbundenen Probleme in Kauf zu nehmen.
Haupterkenntnisse
- Die Argumente bezüglich der Bindung an Vercel und der Migrationen des App-Routers sind berechtigt, gelten aber nicht für React-Kunden, die von einem nicht-JavaScript-Backend bedient werden.
- Auch ohne Meta-Framework müssen Sie weiterhin Formulare sowie Schichten für das Laden und Normalisieren von Daten erstellen – Sie bauen sie nur selbst und sind auf unbestimmte Zeit dafür verantwortlich.
- Eigene Infrastrukturen entwickeln sich oft zu komplizierten Komponenten; setzen Sie bewusst eine Trennung der Aufgaben durch und dokumentieren Sie gemeinsam genutzte Funktionen.
- Die Grenze zwischen Client und Server liegt in Ihrer Verantwortung: Zentralisieren Sie die Serialisierung, definieren Sie den Auftragsvertrag für die Daten und testen Sie alles am echten Backend.