React Apps dauerhaft schlank halten
Bundle Analyse in React Apps ist kein einmaliges Optimierungsprojekt, sondern ein wiederkehrender Audit-Prozess. Ohne Visualizer-Tools, Größen-Budgets in der CI und regelmäßige Prüfung auf doppelte Abhängigkeiten wächst jedes Bundle unbemerkt, bis Ladezeiten und Core Web Vitals spürbar leiden.
Inhaltsverzeichnis
- 1. Warum Bundle-Größe ein Audit-Prozess ist, kein einmaliges Thema
- 2. Bundle Analyse mit vite-plugin-visualizer und Source Map Explorer
- 3. Bundle-Größen-Budgets definieren und in CI durchsetzen
- 4. Doppelte Abhängigkeiten und Versionskonflikte aufspüren
- 5. Route-basiertes Code Splitting systematisch auditieren
- 6. Tree Shaking: warum es oft nicht greift
- 7. Dynamische Imports für schwere Bibliotheken
- 8. Kontinuierliches Monitoring in Pull Requests
- 9. Analyse-Tools im Überblick
- 10. Zusammenfassung
- 11. FAQ
1. Warum Bundle-Größe ein Audit-Prozess ist, kein einmaliges Thema
Viele Teams betreiben Bundle Analyse genau einmal, kurz vor einem großen Release, optimieren die größten Auffälligkeiten und vergessen das Thema danach für Monate. Das Problem: jede neue Abhängigkeit, jede neue Komponente und jeder importierte Icon-Satz trägt ein wenig zur Bundle-Größe bei, und diese kleinen Zuwächse summieren sich über ein Jahr zu einem deutlich langsameren Ladeverhalten, ohne dass ein einzelner Commit dafür verantwortlich gemacht werden könnte.
Bundle Analyse muss deshalb als kontinuierlicher Audit-Prozess verstanden werden, nicht als abgeschlossenes Projekt. Ein einmaliges Aufräumen bringt kurzfristig Verbesserung, verhindert aber nicht, dass sich derselbe Zustand innerhalb weniger Monate wiederholt, wenn keine strukturellen Kontrollen wie CI-Budgets etabliert werden. Die folgenden Abschnitte zeigen konkrete Tools und Prozesse für eine wiederkehrende Bundle Analyse, die Regressionen verhindert statt sie nachträglich zu reparieren.
Der Kern eines guten Audit-Prozesses für Code Splitting und Bundle-Größe besteht aus drei Bausteinen: Sichtbarkeit durch Visualizer-Tools, harte Grenzen durch CI-Budgets, und systematisches Vorgehen bei doppelten Abhängigkeiten. Alle drei werden im Folgenden mit konkreten Konfigurationen gezeigt.
2. Bundle Analyse mit vite-plugin-visualizer und Source Map Explorer
Der erste Schritt jeder Bundle Analyse ist Sichtbarkeit: welches Modul trägt wie viel zur finalen Bundle-Größe bei. Für Vite-basierte React-Projekte liefert rollup-plugin-visualizer eine interaktive Treemap-Darstellung, die nach jedem Build als HTML-Datei erzeugt wird und exakt zeigt, welche Abhängigkeit wie viele Kilobyte im finalen Bundle belegt.
// vite.config.ts — generate a bundle visualization after every build
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { visualizer } from "rollup-plugin-visualizer";
export default defineConfig({
plugins: [
react(),
visualizer({
filename: "dist/stats.html",
gzipSize: true,
brotliSize: true,
template: "treemap", // "sunburst" and "network" are also available
}),
],
});
Für Create-React-App-Projekte oder bereits gebaute Bundles ohne erneuten Build eignet sich source-map-explorer, das vorhandene Source Maps ausliest und dieselbe Art von Treemap direkt aus dem dist-Verzeichnis erzeugt. Beide Werkzeuge lösen dieselbe Aufgabe für die Bundle Analyse: sie machen sichtbar, was sonst als abstrakte Gesamtgröße im Netzwerk-Tab der Entwicklertools verborgen bleibt, und erlauben es, gezielt die größten Einzelposten zu identifizieren, statt blind zu optimieren.
3. Bundle-Größen-Budgets definieren und in CI durchsetzen
Sichtbarkeit allein verhindert keine Regression, wenn niemand regelmäßig hinschaut. Ein Bundle-Größen-Budget macht daraus eine automatisierte Prüfung: überschreitet ein Build eine definierte Grenze, schlägt die CI-Pipeline fehl, bevor der Pull Request gemerged werden kann. Für Bundle Analyse in der Praxis ist bundlesize oder das modernere size-limit die gängige Wahl.
{
"size-limit": [
{
"name": "Main bundle (gzip)",
"path": "dist/assets/index-*.js",
"limit": "180 KB",
"gzip": true
},
{
"name": "Checkout route chunk",
"path": "dist/assets/checkout-*.js",
"limit": "60 KB",
"gzip": true
},
{
"name": "Vendor chunk",
"path": "dist/assets/vendor-*.js",
"limit": "220 KB",
"gzip": true
}
]
}
In der CI-Pipeline genügt danach ein einziger Aufruf, um jeden Pull Request gegen diese Budgets zu prüfen: npm run build && npx size-limit. Überschreitet ein Chunk seine Grenze, beendet sich der Befehl mit einem Nicht-Null-Exit-Code, wodurch der CI-Schritt fehlschlägt und der Pull Request nicht gemerged werden kann, bevor die Bundle-Größe wieder innerhalb der Budgets liegt.
Der entscheidende Effekt dieser Budgets für Bundle Analyse: eine neue, schwere Abhängigkeit fällt sofort im Pull Request auf, bevor sie überhaupt gemerged wird, statt erst Monate später bei einer aufwendigen Nachanalyse entdeckt zu werden. Die Grenzwerte sollten realistisch anhand des aktuellen Ist-Zustands gesetzt werden, mit etwas Puffer, statt willkürlich niedrig, um ständige Fehlalarme zu vermeiden.
4. Doppelte Abhängigkeiten und Versionskonflikte aufspüren
Ein häufig übersehener Bundle-Größen-Treiber, den eine oberflächliche Bundle Analyse leicht verpasst, sind doppelte Versionen derselben Bibliothek im Bundle. Das passiert typischerweise, wenn zwei verschiedene Abhängigkeiten jeweils eine eigene, inkompatible Version derselben Bibliothek als Peer Dependency verlangen, sodass der Package Manager beide Versionen parallel installiert.
Der Treemap-Visualizer aus Abschnitt zwei zeigt solche Duplikate meist bereits optisch als zwei separate Blöcke mit demselben Namen, aber unterschiedlicher Version. Zusätzlich hilft npm ls <paketname>, alle installierten Versionen eines Pakets im gesamten Abhängigkeitsbaum aufzulisten. Für Bundle Analyse-Zwecke ist außerdem das Rollup-Plugin rollup-plugin-duplicate-package-checker-plugin nützlich, das den Build fehlschlagen lässt, sobald mehrere Versionen desselben Pakets im Ausgabe-Bundle landen. Die Lösung ist meist ein overrides-Eintrag in package.json, der den Package Manager zwingt, eine einheitliche Version zu verwenden.
5. Route-basiertes Code Splitting systematisch auditieren
Route-basiertes Code Splitting mit React.lazy gehört zu den wirkungsvollsten Hebeln gegen aufgeblähte initiale Bundles, wird aber in gewachsenen Codebasen häufig nur an einigen Stellen konsequent umgesetzt und an anderen vergessen. Ein systematischer Audit prüft jede Top-Level-Route der Anwendung darauf, ob sie tatsächlich per lazy() geladen wird oder versehentlich Teil des Hauptbundles geblieben ist.
// router.tsx — auditing route-level code splitting
import { lazy, Suspense } from "react";
import { createBrowserRouter } from "react-router-dom";
// Correct: each route is its own chunk, loaded on demand
const CheckoutPage = lazy(() => import("./pages/CheckoutPage"));
const AdminDashboard = lazy(() => import("./pages/AdminDashboard"));
// Anti-pattern found during an audit: direct import keeps this
// in the main bundle even though it is rarely visited
// import { ReportsPage } from "./pages/ReportsPage";
const ReportsPage = lazy(() => import("./pages/ReportsPage"));
export const router = createBrowserRouter([
{
path: "/checkout",
element: (
<Suspense fallback={<PageSkeleton />}>
<CheckoutPage />
</Suspense>
),
},
{
path: "/admin",
element: (
<Suspense fallback={<PageSkeleton />}>
<AdminDashboard />
</Suspense>
),
},
{
path: "/reports",
element: (
<Suspense fallback={<PageSkeleton />}>
<ReportsPage />
</Suspense>
),
},
]);
Bei der Bundle Analyse zeigt sich ein solcher Anti-Pattern deutlich im Treemap: eine Route, die im Alltag selten besucht wird, etwa ein Admin-Bereich oder eine Reporting-Seite, taucht trotzdem im Hauptbundle auf und wird von jedem Besucher geladen, unabhängig davon, ob er die Route je aufruft. Das systematische Auditieren aller Routen gegen diese Checkliste ist eine der ertragreichsten Maßnahmen bei jeder wiederkehrenden Bundle Analyse.
6. Tree Shaking: warum es oft nicht greift
Tree Shaking soll ungenutzten Code automatisch aus dem Bundle entfernen, funktioniert aber nur zuverlässig, wenn bestimmte Voraussetzungen erfüllt sind. Der häufigste Grund, warum Tree Shaking bei einer Bundle Analyse nicht wie erwartet greift, ist der Import-Stil: import _ from "lodash" zieht die komplette Bibliothek in das Bundle, während import debounce from "lodash/debounce" ausschließlich diese eine Funktion einbindet.
Ein zweiter, subtilerer Grund sind Seiteneffekte in importierten Modulen. Bundler müssen konservativ vorgehen und dürfen Code nicht entfernen, wenn dessen Import theoretisch einen Seiteneffekt auslösen könnte, etwa eine globale Registrierung. Das Feld "sideEffects": false in package.json einer eigenen Bibliothek signalisiert dem Bundler explizit, dass ungenutzte Exporte gefahrlos entfernt werden dürfen. Für Bundle Analyse-Zwecke lohnt sich eine gezielte Prüfung, ob häufig genutzte Bibliotheken wie Icon-Sets oder UI-Kits dieses Feld korrekt setzen, da fehlendes Tree Shaking gerade bei großen Icon-Bibliotheken zu unnötig aufgeblähten Bundles führt.
7. Dynamische Imports für schwere Bibliotheken
Manche Bibliotheken sind grundsätzlich schwer, unabhängig von Tree Shaking, etwa Chart-Bibliotheken, Rich-Text-Editoren oder PDF-Generatoren. Für diese lohnt sich ein dynamischer Import, der die Bibliothek erst lädt, wenn die entsprechende Funktion tatsächlich genutzt wird, statt sie beim initialen Seitenaufruf mitzuladen, selbst wenn die Funktion nie aufgerufen wird.
// ChartWidget.tsx — loading a heavy charting library only when needed
import { useState, useEffect, type ComponentType } from "react";
interface ChartProps {
data: number[];
}
export function ChartWidget({ data }: ChartProps) {
const [ChartComponent, setChartComponent] = useState<ComponentType<ChartProps> | null>(null);
useEffect(() => {
// recharts (roughly 90 KB gzipped) is only fetched once this component mounts
import("./RechartsWrapper").then((module) => {
setChartComponent(() => module.default);
});
}, []);
if (!ChartComponent) {
return <div className="h-64 animate-pulse bg-slate-100 rounded-lg" />;
}
return <ChartComponent data={data} />;
}
Für eine gründliche Bundle Analyse lohnt sich eine Liste aller Bibliotheken über einer bestimmten Größenschwelle, etwa 30 Kilobyte gzippt, mit der Frage, ob jede davon wirklich beim initialen Laden gebraucht wird. Chart-Bibliotheken, die nur auf einem Dashboard-Tab sichtbar sind, PDF-Export-Funktionen, die nur bei Klick auf einen Button ausgelöst werden, und Rich-Text-Editoren, die nur im Admin-Bereich vorkommen, sind klassische Kandidaten für dynamische Imports.
8. Kontinuierliches Monitoring in Pull Requests
Damit Bundle Analyse tatsächlich zum wiederkehrenden Prozess wird statt zur einmaligen Aktion, muss sie in den täglichen Entwicklungsworkflow integriert werden. Ein GitHub-Actions-Bot, der die Bundle-Größenänderung direkt als Kommentar in jedem Pull Request postet, macht die Auswirkung einer Änderung sofort sichtbar, ohne dass jemand manuell einen Report öffnen muss.
# .github/workflows/bundle-comment.yml
name: Bundle Size Comment
on:
pull_request:
branches: [main]
jobs:
comment-bundle-diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Compare bundle size against main
uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
script: "npm run build"
Diese Automatisierung ist der wichtigste Schritt, um Bundle Analyse von einer sporadischen Übung in einen festen Bestandteil des Reviews zu verwandeln. Reviewer sehen direkt im Pull Request, ob eine Änderung das Hauptbundle um 500 Bytes oder um 40 Kilobyte vergrößert, und können bei größeren Sprüngen gezielt nachfragen, ob ein dynamischer Import oder ein kleinerer Ersatz für eine neue Abhängigkeit infrage kommt.
9. Analyse-Tools im Überblick
Für unterschiedliche Zwecke innerhalb eines Bundle Analyse-Prozesses eignen sich unterschiedliche Werkzeuge. Die folgende Tabelle ordnet die wichtigsten Optionen nach ihrem primären Anwendungsfall.
| Tool | Zweck | Läuft in CI |
|---|---|---|
| rollup-plugin-visualizer | Interaktive Treemap nach jedem Build | Optional, meist manuell genutzt |
| source-map-explorer | Treemap aus bestehenden Source Maps | Optional, meist manuell genutzt |
| size-limit | Harte Budgets pro Chunk durchsetzen | Ja, blockiert Merge bei Überschreitung |
| duplicate-package-checker | Doppelte Paketversionen erkennen | Ja, als Build-Schritt |
| size-limit-action | Größenänderung als PR-Kommentar posten | Ja, primär für CI gedacht |
In der Praxis ergänzen sich diese Tools: Visualizer-Tools dienen der punktuellen, tiefen Analyse bei größeren Optimierungsrunden, während size-limit und Duplicate-Checker die kontinuierliche Überwachung in der CI-Pipeline übernehmen. Eine vollständige Bundle Analyse-Strategie kombiniert beide Kategorien, statt sich nur auf eine zu verlassen.
Mironsoft
Bundle Analyse, Code Splitting und Performance-Audits für React
Wächst euer React Bundle unbemerkt von Release zu Release?
Wir führen eine vollständige Bundle Analyse eurer React App durch, identifizieren doppelte Abhängigkeiten und fehlendes Code Splitting, und richten CI-Budgets ein, die Regressionen dauerhaft verhindern.
Bundle-Audit
Tiefgehende Analyse mit Visualizer-Tools und konkreten Optimierungsvorschlägen
CI-Budgets einrichten
Größen-Budgets pro Chunk, die Regressionen automatisch im Pull Request blockieren
Code-Splitting-Rollout
Route-basiertes Splitting und dynamische Imports systematisch nachrüsten
10. Zusammenfassung
Bundle Analyse entfaltet ihren Wert erst als wiederkehrender Audit-Prozess, nicht als einmalige Aufräumaktion. Visualizer-Tools wie rollup-plugin-visualizer und source-map-explorer schaffen die nötige Sichtbarkeit, während size-limit in der CI-Pipeline harte Grenzen durchsetzt, bevor eine Regression überhaupt gemerged werden kann.
Route-basiertes Code Splitting mit React.lazy, konsequentes Tree Shaking durch korrekten Import-Stil und dynamische Imports für schwere Bibliotheken sind die konkreten technischen Hebel, die eine Bundle Analyse typischerweise aufdeckt. Wer diese Prüfungen automatisiert in Pull Requests integriert, verhindert das schleichende Wachstum, das ohne Kontrolle jede React-Anwendung über Monate hinweg spürbar langsamer macht.
Bundle Analyse und Code Splitting Audits — Das Wichtigste auf einen Blick
Sichtbarkeit schaffen
rollup-plugin-visualizer und source-map-explorer zeigen als Treemap, welches Modul wie viel Bundle-Größe belegt.
CI-Budgets durchsetzen
size-limit prüft jeden Pull Request gegen definierte Grenzen und blockiert Merges bei Überschreitung.
Doppelte Abhängigkeiten
Versionskonflikte führen zu doppelt gebündelten Bibliotheken, npm ls und Duplicate-Checker decken sie auf.
Route-Splitting und Dynamic Import
React.lazy pro Route und dynamische Imports für schwere Bibliotheken reduzieren das initiale Bundle spürbar.