Wie sich die Frontend-Entwicklung von der Gestaltung zu Skalierung von Systemen entwickelt hat
Die Frontend-Entwicklung entwickelte sich von HTML und CSS zu React-Komponenten, Caching, Monorepo-Tools sowie Observability. Was änderte sich in jedem Schritt und warum das der Fall war.
Quellen: I Thought Frontend Was Colors and Fonts. Then I Shipped to a Million Users.
Die Tools haben sich weiterentwickelt, aber unser Denken hat sich noch stärker verändert.
Fragen Sie jemanden, was Frontend-Entwicklung bedeutet, und Sie erhalten in der Regel dieselbe Antwort: HTML, CSS, JavaScript. Farben, Schriftarten, Bilder, Layout.
Das ist ein guter Ausgangspunkt. Doch die Geschichte endet dort nicht.
Was ursprünglich darin bestand, Webseiten zusammenzustellen, hat sich zu der Entwicklung vollständiger Systeme entwickelt, die schnell, korrekt und nachvollziehbar bleiben müssen, während Tausende von Menschen gleichzeitig damit interagieren.
Die Tools haben sich entlang dieses Weges weiterentwickelt. Doch noch mehr hat sich die Art der Fragen verändert, die wir uns stellen gelernt haben.
Hier ist der vollständige Weg.
1. Es begann mit HTML, CSS und JavaScript
Die frühe Webwelt hielt sich an einfache Prinzipien.
HTML kümmerte sich um die Struktur. CSS um die Darstellung. JavaScript fügte etwas Interaktivität hinzu.
Die Aufgabe ließ sich in einem einzigen Satz zusammenfassen:
Eine Seite erstellen, die gut aussieht und ordnungsgemäß funktioniert.
Die meisten Seiten waren statisch. Server lieferten vollständiges HTML, Browser rendernten es – und der Browser selbst enthielt fast keine Logik.
Mit der Zeit übernahmen die Seiten mehr Verantwortung. Formulare begannen, Eingaben zu überprüfen, während die Nutzer tippten. Der Inhalt wurde aktualisiert, ohne dass eine vollständige Seitenlade stattfand.
JavaScript übernahm immer mehr Aufgaben und übergab nie wieder welche zurück.
- Von Seiten zu Anwendungen
Webseiten verwandelten sich in Anwendungen. Das bedeutete dynamische Benutzeroberflächen, Aufrufe von APIs, Formulare mit mehreren Schritten, Authentifizierungsprozesse sowie Daten, die zwischen den Interaktionen im Browser gespeichert blieben.
Diese Veränderung brachte ein Problem zum Vorschein, das mit den alten Techniken nicht gelöst werden konnte.
Wenn man den DOM direkt manipuliert, wird der sichtbare Bildschirm zur einzigen Quelle der Wahrheit. Ein Datensatz kann in einer Variablen, innerhalb eines DOM-Nodes sowie in einer Antwort vom Server vorhanden sein. Nichts garantiert, dass diese drei Quellen übereinstimmen.
Wenn man eine davon aktualisiert und die anderen ignoriert, entsteht ein Fehler, der nur bei einer bestimmten Klicksequenz auftritt.
Es war daher notwendig, eine Möglichkeit zu schaffen, um anzugeben wie die Benutzeroberfläche aussehen soll, und die Aufgabe, die tatsächlichen DOM-Operationen auszuführen, an etwas anderes zu delegieren.
3. React: Denken in Komponenten
Reacts eigentlicher Durchbruch lag nicht in seiner Syntax, sondern im dahinterstehenden Denkmodell.
UI = f(state)
Man beschreibt den Bildschirm für einen bestimmten Zustand, und das Framework ermittelt die kleinste mögliche Anzahl an DOM-Updates, um dorthin zu gelangen.
Anstelle von element.appendChild(...) schreibt man einfach das aus, was zutreffen sollte.
Die grundlegende Arbeitseinheit weicht nun von der gesamten Seite ab und konzentriert sich stattdessen auf die Komponente. Man denke an eine einzelne Schaltfläche, ein Kartenbild mit einem Produkt, ein Pop-up-Fenster oder einen Schritt im Bestellprozess. Jede dieser Komponenten verfügt über ihre eigene Markup-Struktur, ihr eigenes Styling und ihr eigenes Verhalten.
Das sind die Vorteile dieses Ansatzes:
Wiederverwendbarkeit. Man entwickelt eine Komponente einmal und kann sie an Dutzenden Stellen einsetzen.
Lokales Verständnis. Man kann eine Komponente verstehen, ohne die gesamte Anwendung durchzulesen.
Testbarkeit. Man gibt ihr Eigenschaften ein, prüft die Ausgabe – ein Browser ist nicht erforderlich.
Kompositionierbarkeit. Kleine Komponenten können zu größeren zusammengefügt werden.
Die zentrale Frage wandelte sich von "Wie baue ich diese Seite?" zu "Welche Komponenten bilden diese Anwendung?"
4. Next.js: von der Benutzeroberfläche zur Architektur
Purely client-seitiges React hatte seinen Preis.
Der Browser lädt zunächst eine leere HTML-Struktur herunter. Danach wird ein erheblich großer JavaScript-Bundle heruntergeladen, dieser wird ausgeführt und anschließend die tatsächlichen Daten abgerufen. Erst danach sieht der Benutzer Inhalt.
Das funktioniert gut auf einem leistungsstarken Laptop. Auf einem Mittelklasse-Handy mit schwachem Signal bedeutet das jedoch, mehrere Sekunden lang auf einen leeren Bildschirm starren zu müssen. Zudem könnten Suchmaschinen letztendlich gar nichts Nützliches indizieren.
Frameworks wie Next.js gaben Entwicklern die Kontrolle darüber, wo und wann tatsächlich HTML erzeugt wird.
SSG. Die Darstellung erfolgt zur Zeit der Erstellung, und eine statische Datei wird über ein CDN bereitgestellt. Es ist die schnellste Option, doch der Inhalt bleibt bis zur nächsten Erstellung unverändert.
ISR. Der Inhalt bleibt statisch, regeneriert sich aber im Hintergrund nach einem definierten Zeitraum. Man erhält eine Geschwindigkeit auf Statik-Niveau, wobei der Inhalt recht aktuell bleibt.
SSR. Die Darstellung erfolgt für jede Anfrage auf dem Server. Der Inhalt ist stets aktuell, doch der Server muss jedes Mal erneut arbeiten.
CSR. Die Darstellung erfolgt vollständig im Browser. Dies eignet sich für Seiten, die hinter einem Anmeldevorgang geschützt sind, wo eine Indizierung keine Rolle spielt.
Die wichtigste Erkenntnis: Man wählt pro Route, nicht für die gesamte Anwendung auf einmal.
Eine Marketingseite profitiert von SSG. Eine Produktseite profitiert von ISR. Ein Dashboard profitiert von CSR.
Auch hier kommt die Flüssigkeitszufuhr ins Spiel. Der Server liefert HTML, und anschließend führt der Client den Komponentenbaum erneut aus, um Ereignishandler zu verbinden. Visuell erscheint die Seite bereits fertig, bevor sie tatsächlich interaktiv ist.
React Server Components und Streaming existieren größtenteils, um diese Lücke zu schließen. HTML wird so schnell wie möglich gesendet, und für Teile der Seite, die nie interaktiv sein sollten, wird weniger JavaScript mitgeliefert.
In dieser Phase ist die Arbeit am Frontend nicht mehr nur „die Benutzeroberfläche“. Entscheidungen bezüglich der Darstellungsstrategie, des Routings und des Cachens werden zu architektonischen Entscheidungen, und Frontend-Entwickler sind es, die sie treffen.
5. Dann kam die Skalierbarkeit
Eine Anwendung zu entwickeln, ist eine Herausforderung. Eine Anwendung zu bauen, die Millionen von Nutzern bedient, ist eine völlig andere Herausforderung.
Die gestellten Fragen beginnen sich zu ändern:
Was sollte passieren, wenn eine nachgelagerte API langsamer wird anstatt sauber zu versagen?
Wie liefern sechs separate Teams Code in dieselbe Codebasis, ohne sich gegenseitig zu behindern?
Wie kann man eine Änderung erst an 1 % der Nutzer ausrollen und sie innerhalb einer Minute wieder zurückziehen, falls etwas schiefgeht?
Wie verhält sich die App auf einem drei Jahre alten Android-Telefon an einem Ort, an dem man sie noch nie getestet hat?
Welche der gestrigen Bereitstellungen ist für den heutigen Anstieg der Fehler verantwortlich?
Das sind systemische Fragen. Sie tauchen einfach zufällig im Browser auf.
6. Geschwindigkeit ist keine optionale Verbesserung, sie ist eine grundlegende Erwartung
Eine elegante Benutzeroberfläche, die sechs Sekunden zum Rendern braucht, ist im Grunde genauso kaputt. Die Nutzer spüren die Verzögerung bereits, bevor sie überhaupt benennen können, was nicht stimmt.
Die heutige Leistungsdisziplin stützt sich auf Messungen statt auf Intuition. Core Web Vitals bieten Teams eine gemeinsame Sprache, um über Geschwindigkeit zu sprechen.
LCP (Largest Contentful Paint) markiert den Moment, in dem der Hauptinhalt sichtbar wird. Ziel ist es, unter 2,5 Sekunden zu bleiben.
INP (Interaction to Next Paint) misst, wie schnell die Benutzeroberfläche auf Berührungen oder Klicks reagiert. Ziel sind unter 200 Millisekunden.
CLS (Cumulative Layout Shift) misst, inwieweit Elemente während des Ladens der Seite hin- und herwandern. Ziel ist ein Wert unter 0,1.
Jede Optimierungsmethode bezieht sich auf eine dieser Metriken.
Trennen des Codes und verzögertes Laden. Senden Sie nur den JavaScript-Code, den der aktuelle Pfad tatsächlich benötigt – nicht das gesamte Anwendungs-Paket. Dadurch verbessern sich sowohl LCP als auch INP.
Anpassung der Bilder. Verwenden Sie moderne Formate, passen Sie die Größe richtig an und deklarieren Sie immer im Voraus Breite und Höhe, damit nichts unerwartet erscheint. Dies trägt zu besseren Werten bei LCP und CLS bei.
Bewusste Handhabung der Schriftarten. Laden Sie sie im Voraus, wenden Sie font-display: swap an und beschränken Sie den Zeichensatz auf das, was tatsächlich verwendet wird. Dadurch wird CLS vorgebeugt.
Versand über ein CDN. Die Bereitstellung von Daten von einem Server, der geografisch in der Nähe des Benutzers liegt, verbessert fast alle Metriken gleichzeitig.
Auslagern von aufwendigen Aufgaben vom Hauptthread. Der Browser verfügt über einen einzigen Thread, der sowohl die Darstellung als auch Ihre Skripte verarbeitet. Eine Aufgabe, die 300 Millisekunden dauert, blockiert jeden Klick während dieser Zeit. Hier entstehen die Vorteile von INP.
Das zugrundeliegende Prinzip, einfach ausgedrückt:
Bieten Sie die beste Erfahrung mit möglichst wenigen Ressourcen.
Eine Warnung: Passen Sie anhand dessen an, was Sie tatsächlich gemessen haben, nicht aufgrund von Annahmen. Zahlen, die an einem Entwickler-Notebook in einem Labormilieu erfasst werden, weichen häufig von dem ab, was echte Nutzer vor Ort erleben – und gerade die Felddaten spiegeln die Realität wider.
7. Caching: Vermeiden Sie das erneute Berechnen dessen, was bereits gelöst wurde
Sobald der Traffic zunimmt, ist es verschwenderisch, immer wieder dieselbe Antwort zu generieren. Caching dient dazu, diesen Aufwand nicht zweimal für identische Arbeiten zu betreiben.
Jeder Anfrageweg durchläuft mehrere Ebenen, und jede davon bietet die Möglichkeit, den Arbeitsablauf abzukürzen.
Browser → CDN → Application / ISR → Redis → Database
Je näher die Antwort dem Benutzer geliefert wird, desto günstiger und schneller ist sie.
Doch je näher etwas dem Benutzer im Cache gespeichert ist, desto schwieriger wird es, einen Rückzug zu antreten, falls etwas falsch ist.
Diese Spannung prägt die gesamte Praxis.
Was gehört in einen Cache? Daten, die stabil sind, von mehreren Benutzern geteilt werden und deren Erstellung kostspielig ist. Niemals etwas, das speziell für einen Benutzer bestimmt ist – es sei denn, der Cache-Schlüssel berücksichtigt den einzelnen Benutzer.
Wie lange sollte er bestehen bleiben? Kurze Ablaufzeiten sind sicherer, bringen aber weniger Vorteile. Lange Zeiträume sind schneller, bergen jedoch mehr Risiken.
Wie leert man ihn? Möglichkeiten sind zeitbasierte Ablaufzeiten (TTL), auf Ereignisse gestützte Löschungen (ausgelöst bei Veröffentlichung) oder Dateinamen, die nach ihrem Inhalt gehässt werden, sodass eine Invalidation überhaupt nicht notwendig ist.
Was passiert, wenn eine Abfrage fehlschlägt? Wenn ein häufig genutzter Schlüssel abläuft und gleichzeitig eine Flut an Anfragen beim Backend ankommt, entsteht ein Überlastungsszenario. Schützen Sie sich davor mit einem Schutzmechanismus oder indem Sie die veraltete Version liefern, während eine einzige Anfrage den Neuladen übernimmt.
Muster, das man sich merken sollte, ist stale-while-revalidate. Geben Sie sofort die im Cache gespeicherte Version zurück, aktualisieren Sie sie leise im Hintergrund – der nächste Besucher erhält dann den aktualisierten Inhalt. Die Nutzer spüren die Geschwindigkeit, und Sie gewährleisten die Richtigkeit mit einer kurzen Verzögerung.
Achten Sie darauf, was ein Fehler hier tatsächlich kostet. Ein defekter Cache verursacht nicht nur Verlangsamungen – er kann auch falsche Preise, falsche Namen oder völlig fremde Daten anzeigen.
Caching wirkt wie eine Leistungsoptimierungstechnik, funktioniert aber eigentlich als Entscheidung für die Richtigkeit der Daten.
8. Strukturierung für Wachstum
Sobald mehrere Teams in einer einzigen Codebasis arbeiten, wird die Organisation genauso wichtig wie die Geschwindigkeit der Ausführung.
Grenzen zwischen Modulen. Entscheiden Sie, was öffentlich zugänglich ist, was intern bleibt und welche Abhängigkeiten erlaubt sind.
Verträge für APIs. Typisiert und versioniert, damit Änderungen am Backend die Build-Pipeline stören und nicht die Live-Website.
Wo der Zustand liegt. Serverzustand, URL-Zustand und lokaler Client-Zustand sind grundlegend unterschiedliche Kategorien. Die meisten fehlerbedingten Probleme entstehen dadurch, dass eine Kategorie fälschlicherweise als andere behandelt wird.
Isolierung von Fehlern. Fehlerbegrenzungen sorgen dafür, dass ein defekter Widget nicht die gesamte Seite lahmlegt.
Einführung von Änderungen schrittweise. Feature-Flags, Canary-Deployments sowie die Möglichkeit, sofort rückgängig zu machen.
Die Denkweise ändert sich von der einfachen Funktionsfähigkeit einer Funktion zu der Sicherstellung, dass diese Funktion zukünftige Änderungen nicht erschwert.
9. Gemeinsamer Code in einem Monorepo
Sobald mehrere Anwendungen auf denselben Header, dieselben Design-Tokens sowie dieselben Authentifizierungs-Hilfsfunktionen angewiesen sind, ist das Kopieren und Einfügen von Code nicht mehr nachhaltig.
Ein Monorepo ermöglicht es Teams, UI-Komponenten, Design-Systeme, Hilfsfunktionen, Konfigurationen sowie Typdefinitionen zu teilen – es gibt eine einzige kanonische Quelle der Wahrheit, wobei Änderungen atomar auf alle Pakete übertragen werden.
Der Austausch hat jedoch auch seinen Preis. Jedes geteilte Paket wird zu einer Abhängigkeit, und jede Abhängigkeit fungiert im Grunde wie eine Warteschlange: Eine Aktualisierung eines Teams kann die Veröffentlichung eines anderen Teams verzögern.
Das eigentliche Ziel ist die Wiederverwendung ohne enge Kopplung.
Eine nützliche Heuristik: Teilen Sie Elemente, die selten ändern und unabhängig vom Einsatzort dieselbe Bedeutung haben – wie Design-Tokens, ein Button-Komponente oder Typdefinitionen.
Vermeiden Sie es, etwas zu teilen, was die spezifische Geschäftslogik eines Teams enthält. Das Duplizieren von Code ist oft günstiger als die Verwendung einer falschen gemeinsamen Abstraktion.
Micro-Frontends gehen diese Idee noch weiter und ermöglichen es Teams, unabhängig zu arbeiten – auf Kosten erhöhter Laufzeitkomplexität und doppelter Abhängigkeiten. Dieser Kompromiss kann für große Organisationen mit klaren Grenzen zwischen Teams sinnvoll sein. Außerhalb dieses Kontexts lohnt er sich selten.
10. Das System in der Produktion überwachen
In großem Maßstab verlieren Aussagen wie „Es funktioniert auf meinem Rechner“ oder „Es hat im Staging gut funktioniert“ an Bedeutung.
Was Sie tatsächlich benötigen, ist Einblicke in das Verhalten der Anwendung in der Produktion – auf echten Geräten und in Echtzeit.
Fehlerverfolgung in Kombination mit hochgeladenen Quellkarten, sodass der Stack-Trace auf Ihren tatsächlichen Quellcode verweist und nicht auf minifizierten Code.
Reale Benutzerüberwachung, um Core Web Vitals von echten Nutzern zu erfassen, unterteilt nach Gerät, Land und Verbindungsqualität.
Gestrukturiertes Logging, das mit einer Anfrage-ID verknüpft ist, die Sie vom Browser aus bis zu jedem von ihr angeforderten Backend-Dienst nachverfolgen können.
Alarme, die auf Symptomen basieren. Benachrichtigen Sie die Nutzer, wenn Fehlerraten oder Latenzwerte einen Schwellenwert überschreiten – und nicht einfach nur, wenn die CPU-Nutzung eines Servers in die Höhe schießt.
Verfolgung von Releases. Markieren Sie jeden Fehler mit der spezifischen Bereitstellung, die ihn verursacht hat, damit die Beantwortung der Frage „Was hat sich geändert?“ Sekunden statt Stunden dauert.
Die Fragen, die diese Einrichtung beantworten sollte:
Wo genau stoßen Nutzer auf Fehler? Welcher API-Aufruf beeinträchtigt die Benutzererfahrung? Welcher Pfad ist langsam – und für welche Nutzergruppe? Hat die neueste Bereitstellung die Situation im Vergleich zu vorher verschlechtert?
Früher war der Frontend-Code der schwierigste Teil des Stack, den man beobachten konnte. Das muss nicht mehr so sein.