Frontend-Leistung: Vom blinden Fleck bei der Code-Überprüfung zur Produktmetrik
Erfahren Sie, warum das Bestehen einer Code-Review nicht ausreicht, welche Core Web Vitals tatsächlich wichtig sind, und wie Sie reale Performance-Probleme in React messen und beheben können.
Es hat die Code-Review bestanden
Ihr Pull Request ist in Ordnung. Die Logik stimmt. Alle Tests bestehen. Ein Senior-Engineer hat es genehmigt.
Ihr veröffentlichen die Änderung am Donnerstagnachmittag.
Am Freitagmorgen kommt eine Nachricht vom Produktmanager: „Die Leute sagen, die App wirkt träge.“
Daher starten Sie Chrome DevTools. Auf Ihrem MacBook Pro, das über eine Heim-Optikverbindung verbunden ist und Chrome mit einem Dutzend deaktivierten Erweiterungen läuft, sieht alles reibungslos aus.
Aber Ihre Nutzer haben nicht Ihre Ausstattung.
Viele von ihnen nutzen ein Mittelklasse-Android-Gerät aus einigen Jahren, das über eine 4G-Verbindung verfügt, die häufig auf 3G abfällt. Sie könnten sich in Jakarta, Lagos oder Baku befinden – Orten, wo allein die Netzwerkverzögerung zu 200 bis 400 Millisekunden pro Anfrage hinzukommt.
Ihre App lässt sie warten.
Warum diese Lücke besteht
Die meisten Frontend-Entwickler arbeiten unter nahezu perfekten Bedingungen und bringen ihre Anwendungen in weitaus chaotischeren Umgebungen zum Laufen. Genau diese Diskrepanz ist der Ursprung von Leistungsproblemen.
Das sind die Dinge, die Ihre alltägliche Entwicklungsumgebung vor Ihnen verheimlicht:
CPU-Throttling. Ihr Entwicklungssystem verfügt über reichlich Rechenleistung. Die Chrome DevTools können eine Verlangsamung der CPU um das Vier- oder Sechsfache simulieren, doch kaum jemand macht sich die Mühe, dies zu aktivieren.
Netzwerkbedingungen. Das Testen gegenüber localhost bedeutet eine Latenz von null. Reale Nutzer haben mit Rücklaufzeiten zwischen 100 und 500 Millisekunden zu kämpfen. Ein Datenabruf, der auf Ihrem Rechner sofortig erscheint, kann die Benutzeroberfläche im Live-Betrieb für eine volle Sekunde einfrieren.
Größe des Bundles. Das Hinzufügen einer Bibliothek während des Programmierens erscheint kostenlos – es gibt keine sichtbaren Kosten. In der Produktion kann dieselbe Abhängigkeit jedoch Ihrem ursprünglichen Bundle 80 KB hinzufügen, die ein Nutzer mit 3G vor dem Anzeigen von Inhalten herunterladen muss.
Zeit zum Parsen von JavaScript. Das Herunterladen des Bundles auf das Gerät ist erst der erste Schritt. Der Browser muss es anschließend parsen und ausführen. Auf schwächerer Hardware kann allein das Parsen eines 500 KB großen Bundles 3 bis 4 Sekunden in Anspruch nehmen.
Zusammen führen diese Faktoren zu einer echten Kluft zwischen dem Eindruck, den Ihre App bei Ihnen hinterlässt, und dem Eindruck, den sie bei den tatsächlichen Nutzern erweckt – und diese Kluft bleibt in der Regel unsichtbar, bis ein Beschwerdefall sie ans Licht bringt.
Warum Leistung eine Produktentscheidung ist
Frontend-Entwickler ordnen die Leistung oft unter „technische Details“ ein. Produktmanager ignorieren sie meist völlig, bis es zu einer Notlage kommt.
Außerhalb dieser Ansätze hält keiner stand.
Leistung gehört in Produktgespräche, denn sie prägt die Ergebnisse, die ein Unternehmen tatsächlich verfolgt.
Einnahmen. Amazon hat berichtet, dass jede zusätzliche Verzögerung von 100 Millisekunden ihnen etwa 1 % an Umsätzen kostet. Bei täglichen Einnahmen in Höhe von einer Milliarde Dollar entspricht das 10 Millionen Dollar pro 100 Millisekunden. Kleinere Unternehmen weisen geringere absoluten Zahlen auf, doch das Verhältnis bleibt gleich.
Bewahrung der Kunden. Mehr als die Hälfte der mobilen Besucher – 53 % – verlassen eine Seite, die länger als 3 Sekunden zum Laden benötigt. Sie beschweren sich selten; sie gehen einfach und kehren nie zurück.
SEO. Seit 2021 berücksichtigt Google die Core Web Vitals in seinem Ranking-Algorithmus. Eine langsame Ladezeit sorgt dafür, dass Ihre Website weiter unten auf der Ergebnisseite erscheint – was bedeutet, dass weniger Menschen sie entdecken.
Barrierefreiheit. Geschwindigkeit ist außerdem ein Thema der Gleichberechtigung. Menschen, die ältere Geräte und langsamere Internetverbindungen nutzen, sind überproportional in Schwellenländern und einkommensschwachen Gruppen vertreten. Eine langsame App schließt effektiv einen Teil Ihrer Zielgruppe aus.
Sobald Sie das Gespräch auf Einnahmen, Kundenbindung, Suchsichtbarkeit und Barrierefreiheit ausrichten, wirkt Leistungsfähigkeit nicht mehr optional, sondern ist eine grundlegende Voraussetzung.
Die wirklich wichtigen Metriken
Man kann nicht das beheben, was man nicht misst, und man kann nicht messen, was man nicht definiert hat. Genau da wird ein gemeinsamer Begriffsschatz für Leistungsfähigkeit unerlässlich.
Googles Core Web Vitals bieten derzeit das zuverlässigste Framework dafür.
LCP — Largest Contentful Paint
Dies misst, wie lange es dauert, bis das größte sichtbare Element auf der Seite dargestellt wird – im Grunde genommen, wann der Benutzer das Gefühl hat, die Seite sei „geladen“.
Gut: unter 2,5 Sekunden. Braucht Verbesserung: zwischen 2,5 und 4 Sekunden. Schlecht: über 4 Sekunden.
Häufige Ursachen: zu große, unoptimierte Bilder, Ressourcen, die die Darstellung blockieren, sowie langsame Antworten des Servers.
INP — Interaction to Next Paint
Dies misst die Verzögerung zwischen einer Benutzeraktion – Klick, Tippen oder Tastenanschlag – und dem Moment, in dem der Bildschirm visuell reagiert. Er hat 2024 FID (First Input Delay) als Standardmetrik für Interaktivität abgelöst.
Gut: unter 200 ms. Besserung erforderlich: zwischen 200 und 500 ms. Schlecht: über 500 ms.
Häufige Ursachen: aufwendige Berechnungen im Hauptthread sowie synchroner Code, der die Darstellung blockiert.
CLS – Cumulative Layout Shift
Dies misst, wie stark sich der Inhalt während des Ladevorgangs unerwartet verschiebt. Die Werte reichen von 0 (keine Verschiebung) nach oben, wobei Werte über 1 als schwerwiegend gelten.
Gut: unter 0,1. Besserung erforderlich: zwischen 0,1 und 0,25. Schlecht: über 0,25.
Häufige Ursachen: Bilder ohne explizite Breite und Höhe, dynamisch nach dem Laden eingefügter Inhalt sowie Web-Schriften, die spät geladen werden.
Wie man misst: Ihr Werkzeugkasten
Lighthouse (beginnen Sie hier)
Öffnen Sie die Chrome DevTools, wechseln Sie zur Registerkarte Lighthouse und führen Sie eine Überprüfung mit aktiviertem Throttling in einem mobilen Profil durch.
Lighthouse gibt für Leistung, Zugänglichkeit, SEO und Best Practices eine Bewertung zwischen 0 und 100 an. Besonders nützlich ist, dass es erklärt, warum genau diese Bewertung erzielt wurde, und darauf hinweist, was zuerst behoben werden sollte.
# Or run it from the CLI for CI/CD integration
npm install -g lighthouse
lighthouse https://yourapp.com --output html --output-path report.html
Wichtig: Führen Sie Lighthouse jedes Mal in einem Incognito-Fenster aus. Installierte Browsererweiterungen können die Ergebnisse verfälschen.
React DevTools Profiler
Trotz seiner hohen Aufschlusskraft ist dies wohl das am wenigsten genutzte Werkzeug für React-Entwickler.
Um es zu verwenden, öffnen Sie die React DevTools, wechseln Sie zur Registerkarte Profiler, klicken Sie auf „Aufnehmen“, interagieren Sie mit Ihrer Anwendung und stoppen Sie anschließend die Aufnahme.
Das Ergebnis ist ein Flammengraph, der jede Ausführung zeigt – welche Komponenten aktiviert wurden, was sie ausgelöst hat und wie lange jede Ausführung gedauert hat.
What to look for:
- Components rendering more than they should
- Renders triggered by unrelated state changes
- Expensive components re-rendering on every keystroke
Web Vitals-Bibliothek
Falls Sie herausfinden möchten, wie Ihre Anwendung bei tatsächlichen Besuchern abschneidet und nicht nur in einer lokalen DevTools-Sitzung, ist die web-vitals-Bibliothek die richtige Lösung:
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(metric => {
// Send to your analytics service
console.log('LCP:', metric.value);
});onINP(metric => {
console.log('INP:', metric.value);
});onCLS(metric => {
console.log('CLS:', metric.value);
});
Der Ansatz liefert Ihnen Felddaten, die aus echten Benutzer-Sitzungen gesammelt wurden, und nicht Zahlen, die unter künstlichen Laborkonditionen erzeugt wurden.
Die erneute Ausführung, von der Sie nichts wussten
Eines der heimtückischsten Leistungsprobleme bei React macht sich nicht bemerkbar. In der Regel sieht es so aus:
// ❌ Problem: selecting the full user object
function Header() {
const user = useSelector(state => state.user);
return <div>{user.name}</div>;
}
Dieser Komponente wird jedes Mal neu gerendert, wenn sich jede Eigenschaft innerhalb von state.user ändert – unabhängig davon, ob die Komponente diese Eigenschaft tatsächlich liest. Wenn das Benutzerobjekt zwanzig Felder enthält und fünf davon häufig geändert werden, wird Header insgesamt fünfmal öfter neu gerendert, als nötig ist.
// ✅ Fix: select only what you need
function Header() {
const name = useSelector(state => state.user.name);
return <div>{name}</div>;
}
Durch diese Änderung reagiert Header nur noch auf Änderungen in name. Es handelt sich um eine einzeilige Anpassung, doch die Leistungsbeeinträchtigung ist real. Derselbe Ansatz gilt auch für Context:
// ❌ Problem: consuming the full context
function ThemeButton() {
const { theme, user, notifications } = useAppContext();
return <button className={theme}>Click</button>;
}
// ✅ Fix: split contexts by update frequency
const ThemeContext = createContext();
const UserContext = createContext();function ThemeButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Click</button>;
}
Lazy Loading: Beenden Sie das Senden von Code, den Benutzer nicht benötigen
Ein häufiger Fehler in React-Projekten besteht darin, den gesamten Anwendungs-Bundle bereits bei der ersten Seitenladung zu übertragen – einschließlich Code für Routen, die der Besucher noch nicht geöffnet hat und vielleicht niemals öffnen wird.
// ❌ Problem: all routes loaded upfront
import CheckoutPage from './pages/CheckoutPage';
import AdminDashboard from './pages/AdminDashboard';
import SettingsPage from './pages/SettingsPage';
function App() {
return (
<Routes>
<Route path="/checkout" element={<CheckoutPage />} />
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
);
}
// ✅ Fix: lazy load each route
import { lazy, Suspense } from 'react';
const CheckoutPage = lazy(() => import('./pages/CheckoutPage'));
const AdminDashboard = lazy(() => import('./pages/AdminDashboard'));
const SettingsPage = lazy(() => import('./pages/SettingsPage'));function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/checkout" element={<CheckoutPage />} />
<Route path="/admin" element={<AdminDashboard />} />
<Route path="/settings" element={<SettingsPage />} />
</Routes>
</Suspense>
);
}
Durch diese Einstellung wird jede Route zu einem eigenen Teil, sodass die Benutzer nur den Code herunterladen, der für die Seite benötigt wird, die sie tatsächlich ansehen. In größeren Anwendungen kann allein diese Änderung die anfängliche Dateigröße um 40 bis 60 Prozent verringern.
Wann man nicht optimieren sollte: Die useMemo-Falle
Die meisten Leistungsrichtlinien überspringen diesen Punkt: Zu frühige Optimierung kann Ihren Code sogar schädigen.
useMemo und useCallback haben ihren eigenen Aufwand – sie verbrauchen Speicher und vergleichen Abhängigkeiten bei jeder Neuzeichnung. Wenn man sie an der falschen Stelle einsetzt, kann das die Leistung sogar verschlechtern.
// ❌ Unnecessary — this calculation is not expensive
function UserCard({ user }) {
const displayName = useMemo(
() => `${user.firstName} ${user.lastName}`,
[user.firstName, user.lastName]
);
return <div>{displayName}</div>;
}
// ✅ Just compute it — string concatenation is instant
function UserCard({ user }) {
const displayName = `${user.firstName} ${user.lastName}`;
return <div>{displayName}</div>;
}
// ✅ useMemo IS worth it here — genuinely expensive calculation
function DataGrid({ rows, filters }) {
const filteredRows = useMemo(
() => rows.filter(row => matchesAllFilters(row, filters)),
[rows, filters]
);
return <Table rows={filteredRows} />;
}
Das Leitprinzip: Profilieren Sie erst, bevor Sie etwas ändern, optimieren Sie erst danach und messen Sie erneut, um zu überprüfen, ob die Korrektur geholfen hat. Verlassen Sie sich nicht auf Intuition.
Falls der Profiler ein Komponente niemals als Engpass markiert, lassen Sie sie ungespeichert – die zusätzliche Komplexität lohnt sich nicht.
Bildoptimierung: Die einfachsten Lösungen
Bilder sind häufig der größte Faktor für langsame Seitenladezeiten, doch sie gehören auch zu den einfachsten Problemen, die behoben werden können.
// ❌ Unoptimized: full-size image, no lazy loading
<img src="/hero-image.png" />
// ✅ Optimized: modern format, explicit dimensions, lazy loading
<img
src="/hero-image.webp"
width={1200}
height={600}
loading="lazy"
decoding="async"
alt="Hero image"
/>
Einige Gewohnheiten machen wirklich einen Unterschied:
Wechseln Sie von PNG oder JPEG zu WebP oder AVIF. WebP verringert die Dateigröße in der Regel um 25 bis 35 Prozent im Vergleich zu JPEG bei ähnlicher Qualität. AVIF komprimiert noch weiter, doch die Browserunterstützung dafür ist bisher noch nicht so weit verbreitet.
Deklarieren Sie stets explizit die Breiten- und Höhenattribute. Dadurch verhindert man, dass der Browser das Layout nach dem Laden des Bildes verschiebt, was direkt zur Verbesserung Ihres CLS-Wertes beiträgt.
wenden Sie loading="lazy" auf alles an, was unterhalb des Sichtbereichs liegt. Der Browser wartet dann damit, das Bild herunterzuladen, bis der Benutzer es gerade einblenden will.
Fügen Sie für Bilder, die nicht für die erste Darstellung entscheidend sind, decoding="async" hinzu. Dadurch kann der Browser das Bild außerhalb des Hauptthreads decodieren, anstatt die Darstellung zu blockieren.
Die tatsächlichen Kompromisse
Keine Leistungsstrategie ist kostenlos. Hier ist eine ehrliche Aufschlüsselung dessen, was Sie opfern müssen:
Durch die Aufteilung des Codes nach Routen wird das anfängliche Bundle verkleinert, doch es entsteht eine geringe Verzögerung beim ersten Besuch einer neuen Route durch den Benutzer. Das Verschieben des Bildladens auf später erhöht die Anfangsgeschwindigkeit, führt jedoch dazu, dass die Bilder beim Scrollen sichtbar erscheinen. Das Einbetten aufwändiger Berechnungen in useMemo verringert die erneuten Darstellungen, kostet jedoch zusätzliche Codekomplexität und verringert die Lesbarkeit. Das Caching über einen Service Worker macht wiederholte Besuche nahezu sofortig, erfordert aber komplexe Logiken zur Cache-Invalidierung. Serverseitige Rendering oder statische Generierung sorgen für eine schnelle Anzeige und besseres SEO, erfordern jedoch mehr Serverinfrastruktur und erhöhen die Komplexität bei der Aktivierung des Contents.
Das Prinzip hinter all dem: Optimieren Sie niemals für eine Metrik, die Sie noch nicht tatsächlich gemessen haben.
Eine Lighthouse-Bewertung von 95 sagt nichts darüber aus, ob echte Nutzer eine gute Erfahrung haben. Ausstatten Sie Ihre App mit der Web Vitals-Bibliothek, sammeln Sie Daten von tatsächlichen Besuchern, identifizieren Sie die eigentlichen Engpässe, beheben Sie diese konkreten Probleme und messen Sie anschließend erneut, um zu überprüfen, ob es funktioniert hat.
Eine praktische Checkliste
Vor dem Veröffentlichen einer bedeutenden Funktion sollten Sie diese Liste durchgehen:
Performance Checklist
─────────────────────
□ Run Lighthouse on mobile with throttling (target score: 90+)
□ Check bundle size with webpack-bundle-analyzer or source-map-explorer
□ Verify all routes are lazy loaded
□ Confirm images use WebP/AVIF with explicit dimensions
□ Profile with React DevTools — no unnecessary re-renders
□ Check Core Web Vitals in production with web-vitals library
□ Test on a real mobile device, not just DevTools emulation
# Install bundle analyzer
npm install --save-dev webpack-bundle-analyzer
# Or for Vite
npm install --save-dev rollup-plugin-visualize
Fazit
Arbeit an der Leistungsfähigkeit ist nicht ein letzter Dringlichkeitsauftrag kurz vor dem Launch, und es handelt sich auch nicht um eine Schicht, die erst hinzugefügt wird, nachdem eine Funktion bereits funktioniert. Es handelt sich um eine Disziplin – eine Sammlung von Gewohnheiten und Werkzeugen, die in Ihren täglichen Arbeitsablauf integriert sind.
Die Ingenieure, die die schnellsten Anwendungen entwickeln, sind nicht per se talentierter als jene, die langsame Anwendungen erstellen. Sie messen einfach konsequenter. Sie wissen, welches Werkzeug zu welchem Problem passt, und haben einen einfachen Ablauf verinnerlicht: Zuerst Profil erstellen, nur das beheben, was die Daten anzeigen, und anschließend erneut messen, um zu überprüfen, ob es geholfen hat.
Eine Anwendung kann alle Code-Reviews erfolgreich bestehen und dennoch echte Nutzer frustrieren.
Messen. Profil erstellen. Nur das beheben, was wirklich wichtig ist.
Verwandte Artikel
- React-Prop-Drilling und „God Components“ reduzieren – Lernen Sie sieben konkrete Refactoring-Muster, um überdimensionierte React-Komponenten durch Isolierung von State, Datenabruf, Berechtigungen und Ladelogik statt nur durch das Aufteilen von Dateien zu strukturieren.