BLOG

Webpack Fast Refresh vs Vite: Was war schneller für ilert-ui

Jan Arnemann
October 29, 2025
Table of Contents:

Dieser Artikel zeigt, was sich im Tagesgeschäft der Entwicklung von ilert-ui am schnellsten anfühlte – einer großen React- und TypeScript-App mit vielen lazy Routes. Wir sind zunächst von Create React App (CRA) auf moderne Tooling-Stacks umgestiegen, haben Vite für die lokale Entwicklung erprobt und sind letztlich bei webpack-dev-server + React Fast Refresh gelandet.

Scope: Nur lokale Entwicklung. Unsere Production-Builds bleiben auf Webpack. Zum Kontext: Das React-Team hat CRA am 14. Februar 2025 offiziell abgekündigt und empfiehlt die Migration zu einem Framework oder einem modernen Build-Tool wie Vite, Parcel oder RSBuild.

Qualitative Field Notes aus ilert-ui: Wir haben keine formalen Benchmarks durchgeführt; es geht um unsere Alltagserfahrungen in einer großen, per Route gesplitteten App.

Mini‑Glossar

Hier sind die hilfreichen Begriffe, denen Sie in diesem Artikel begegnen werden.

  • ESM: Das native JavaScript-Modulsystem, das Browser verstehen.
  • HMR: Tauscht geänderten Code in einer laufenden App aus, ohne einen vollständigen Reload.
  • React Fast Refresh: Reacts HMR-Erlebnis, das Komponenten-State nach Möglichkeit erhält.
  • Lazy Route / Code-Splitting: Lädt den Code einer Route erst, wenn die Route besucht wird.
  • Vendor Chunk: Ein Bundle gemeinsamer Third-Party-Abhängigkeiten, das über Routen hinweg gecacht wird.
  • Eager Pre-Bundling: Bündelt gemeinsame Abhängigkeiten vorab, um viele kleine Requests später zu vermeiden.
  • Dependency Optimizer (Vite): Pre-bundelt Bare Imports; kann erneut laufen, wenn zur Laufzeit neue Abhängigkeiten entdeckt werden.
  • Type-aware ESLint: ESLint, das TypeScript-Typinformationen nutzt – genauer, aber schwergewichtiger.

Warum wir CRA verlassen haben

Problemstellung: ilert-ui ist den Bequemlichkeits-Defaults von CRA entwachsen, während die App reifte.

Das hat uns von CRA weggetrieben:

  • Customization-Reibung: Fortgeschrittene Webpack-Tweaks (Custom Loader, striktere Split-Chunks-Strategie, Babel-Einstellungen für react-refresh) erforderten Ejecten oder Patching. Das bremste die Iteration in einer Production-Scale-App.
  • Große Abhängigkeitsfläche: react-scripts brachte viele transitive Pakete. Installs wurden langsamer, und die Security-Noise wuchs im Laufe der Zeit – ohne klaren Nutzen für uns.

Ziel für den nächsten Schritt

  • React + TS beibehalten.
  • Time-to-Interactive nach Serverstart verbessern.
  • State bei Edits erhalten (Fast-Refresh-Verhalten) und HMR reaktionsschnell halten.
  • Vorhersagbare First-Visit-Latenz beim Navigieren über viele lazy Routes behalten.

Warum Vite wie die bessere Lösung aussah

Während der Entwicklung dient Vite Ihren Source Code als natives ESM und pre-bundelt Bare Imports aus node_modules mit esbuild. Das liefert üblicherweise sehr schnelle Cold Starts und responsives HMR.

Was wir sofort geliebt haben

  • Cold Starts: Spürbar schneller als unsere CRA-Baseline.
  • Minimale Konfiguration, saubere DX: Sinnvolle Defaults und gut lesbare Fehler.
  • Großartiges HMR in bereits besuchten Bereichen: Edits innerhalb schon besuchter Routen fühlten sich exzellent an.

Wo das Modell an unserer Größe rieb

In Codebasen mit vielen lazy Routes können Erstbesuche Schübe von ESM-Requests auslösen, und wenn zur Laufzeit neue Abhängigkeiten entdeckt werden, läuft der Dependency-Optimizer erneut, was die Seite neu lädt. Das ist erwartetes Verhalten, machte die Cross-Route-Erkundung für uns aber uneinheitlich.

Qualitative Field Notes aus ilert-ui

Methodik: qualitative Beobachtungen aus der täglichen Entwicklung in ilert-ui.

Die Form unseres Repos

  • Dutzende lazy Routes, mehrere schwere Bereiche, die viele Module hereinziehen.
  • Hunderte geteilte Dateien und tiefe Store-Imports über Features hinweg.

Was wir bemerkten

  1. Erstbesuch schwerer Routen: Das Öffnen einer abhängigkeitsträchtigen Route löste oft viele ESM-Requests und manchmal einen erneuten Dep-Optimizer-Lauf aus. Cross-Route-Erkundung über unberührte Routen fühlte sich langsamer an als in unserem Webpack-Setup, das gemeinsame Vendors frühzeitig pre-bundelt.
  2. Typed-ESLint-Overhead: Type-aware ESLint (mit parserOptions.project oder projectService) im Prozess mit dem Dev-Server verursachte Latenz beim Tippen. Linting out-of-process zu verlagern half, kompensierte die Kosten in unserer Größenordnung aber nicht vollständig – ein erwarteter Trade-off bei typisiertem Linting.

TL;DR für unsere Codebase: Vite war fantastisch, sobald eine Route in der Session einmal berührt war, aber die Erstbesuche über viele lazy Routes hinweg waren weniger vorhersehbar.

Warum wir auf webpack-dev-server + React Fast Refresh gewechselt haben

Was wir betreiben

  • webpack-dev-server mit HMR.
  • React Fast Refresh via @pmmmwh/react-refresh-webpack-plugin und react-refresh in Babel.
  • Webpack SplitChunks für gemeinsame Vendor-Bundles; Filesystem-Caching; Source Maps; Error Overlays; ESLint out-of-process.

Warum es sich für unser Team end-to-end schneller anfühlte

  1. Eager Vendor Pre-Bundling: Wir pre-bundeln Vendor-Chunks explizit (React, MUI, MobX, Charts, Editor, Kalender etc.). Der allererste Load ist etwas schwerer, aber Erstbesuche anderer Routen sind schneller, weil gemeinsame Abhängigkeiten bereits gecacht sind. SplitChunks macht das vorhersagbar.
  2. Ergonomie von React Fast Refresh: Solide State-Erhaltung bei Edits, verlässliche Fehlererholung und Overlays, die wir mögen.
  3. Nicht blockierendes Linting: Typed ESLint läuft außerhalb des Dev-Server-Prozesses, sodass HMR selbst während großer Type-Checks responsiv bleibt.

Belege – die Stellschrauben, an denen wir gedreht haben

1// webpack.config.js
2module.exports = {
3  optimization: {
4    minimize: false,
5    runtimeChunk: "single",
6    splitChunks: {
7      chunks: "all",
8      cacheGroups: {
9        "react-vendor": {
10             test: /[\/\]node_modules[\/\](react|react-dom|react-router-dom)[\/\]/,
11          name: "react-vendor",
12          chunks: "all",
13          priority: 30,
14        },
15        "mui-vendor": {
16          test: /[\/\]node_modules[\/\](@mui\/material|@mui\/icons-material|@mui\/lab|@mui\/x-date-pickers)[\/\]/,
17          name: "mui-vendor",
18          chunks: "all",
19          priority: 25,
20        },
21        "mobx-vendor": {
22          test: /[\/\]node_modules[\/\](mobx|mobx-react|mobx-utils)[\/\]/,
23          name: "mobx-vendor",
24          chunks: "all",
25          priority: 24,
26        },
27        "utils-vendor": {
28          test: /[\/\]node_modules[\/\](axios|moment|lodash\.debounce|lodash\.isequal)[\/\]/,
29          name: "utils-vendor",
30          chunks: "all",
31          priority: 23,
32        },
33        "ui-vendor": {
34          test: /[\/\]node_modules[\/\](@loadable\/component|react-transition-group|react-window)[\/\]/,
35          name: "ui-vendor",
36          chunks: "all",
37          priority: 22,
38        },
39        "charts-vendor": {
40          test: /[\/\]node_modules[\/\](recharts|reactflow)[\/\]/,
41          name: "charts-vendor",
42          chunks: "all",
43          priority: 21,
44        },
45        "editor-vendor": {
46 test: /[\/\]node_modules[\/\](@monaco-editor\/react|monaco-editor)[\/\]/,
47          name: "editor-vendor",
48          chunks: "all",
49          priority: 20,
50        },
51        "calendar-vendor": {
52          test: /[\/\]node_modules[\/\](@fullcalendar\/core|@fullcalendar\/react|@fullcalendar\/daygrid)[\/\]/,
53          name: "calendar-vendor",
54          chunks: "all",
55          priority: 19,
56        },
57        "vendor": {
58          test: /[\/\]node_modules[\/\]/,
59          name: "vendor",
60          chunks: "all",
61          priority: 10,
62        },
63      },
64    },
65  },
66};

1// vite.config.ts - Vite optimizeDeps includes we tried
2export default defineConfig({
3  optimizeDeps: {
4    include: [
5      "react",
6      "react-dom",
7      "react-router-dom",
8      "@mui/material",
9      "@mui/icons-material",
10      "@mui/lab",
11      "@mui/x-date-pickers",
12      "mobx",
13      "mobx-react",
14      "mobx-utils",
15      "axios",
16   "moment",
17      "lodash.debounce",
18      "lodash.isequal",
19      "@loadable/component",
20      "react-transition-group",
21      "react-window",
22      "recharts",
23      "reactflow",
24      "@monaco-editor/react",
25      "monaco-editor",
26      "@fullcalendar/core",
27      "@fullcalendar/react",
28      "@fullcalendar/daygrid",
29    ],
30    // Force pre-bundling of these dependencies
31    force: true,
32  },
33});
34

Ergebnis: Half bei einigen Cold Starts, glättete in unserem Repo jedoch die First-Visit-Latenz über viele lazy Routes hinweg nicht so stark wie die eager Vendor-Chunks in Webpack.

Was wir versucht haben, um Vite zu beschleunigen (und was nicht)

Was wir in Vite versucht haben

ESLint in einem separaten Prozess ausführen
Was es tut: Lintet im Hintergrund, statt den Dev-Server zu blockieren.
Auswirkung: Schnelleres Feedback beim Editieren.

Filesystem-Cache aktivieren
Was es tut: Wiederverwendet Build-Ergebnisse über Neustarts hinweg.
Auswirkung: Schnellere Cold Starts und Rebuilds.

Third-Party-Code vorbündeln (Vendor Split)
Was es tut: Bündelt Bibliotheken wie React einmal und hält sie getrennt vom App-Code.
Auswirkung: Weniger Arbeit bei jedem Save; flinkeres HMR.

Diese Tweaks ließen Vite besser wirken – waren aber nicht genug, um unsere größeren Performance-Themen zu lösen. Darum haben wir Webpack evaluiert.

Dinge, die wir hätten versuchen können

Aggressiveres Tuning von optimizeDeps
Warum wir es ausließen: Kann großen Projekten helfen, erfordert aber sorgfältiges Profiling und laufende Dependency-Hygiene. Der Zeitaufwand überwog den erwartbaren Gewinn für uns.

„Warm Crawl“ beim Serverstart
Was es ist: Ein Script, das beim Start Routen besucht, um Module und Caches vorzuladen.
Warum wir es ausließen: Zusätzliche Komplexität und uneinheitlicher Nutzen in realen Projekten.

Versionen für gelinkte Pakete pinnen

Was es ist: Versionen in einem Monorepo sperren, um Vites Re-Optimization-Churn zu reduzieren.
Warum wir es ausließen: In manchen Setups nützlich, aber zusätzlicher Wartungsaufwand; vor einem größeren Rework nicht lohnend.

Vor- und Nachteile (in unserem Kontext)

Vite – Vorteile

  • Rasante Cold Starts und leichtgewichtige Konfiguration.
  • Exzellentes HMR innerhalb bereits berührter Routen.
  • Starkes Plugin-Ökosystem und moderne ESM-Defaults.

Vite – Nachteile

  • Dep-Optimizer-Re-Runs können den Flow beim erstmaligen Navigieren über viele lazy Routes unterbrechen.
  • Erfordert in großen Monorepos und mit gelinkten Paketen sorgfältiges Setup.
  • Typed ESLint im Prozess kann die Responsiveness bei großen Projekten schmälern; besser out-of-process.

Webpack + Fast Refresh – Vorteile

  • Vorhersagbare First-Visit-Latenz über viele Routen dank eager Vendor-Chunks.
  • Feinkörnige Kontrolle über Loader, Plugins und Output.
  • Fast Refresh erhält State und bietet ausgereifte Error Overlays.

Webpack + Fast Refresh – Nachteile

  • Schwererer initialer Load als Vites Cold Start.
  • Mehr Konfigurationsoberfläche zu pflegen.
  • Historische Komplexität (abgemildert durch moderne Config-Patterns und Caching).

Schnelle Performance-Tests, die Sie lokal für Ihr Projekt durchführen können

Diese Checks sind schnelle, menschliche Benchmarks – kein Profiler nötig. Nutzen Sie eine Stoppuhr und DevTools, um reale Interaktionsverzögerungen und die wahrgenommene Flüssigkeit zu vergleichen.

Cold Start

So testen Sie: Starten Sie den Dev-Server neu und messen Sie die Zeit von npm run dev bis zur ersten interaktiven Seitenladung (eine Stoppuhr reicht aus).

Beobachtung: Vite startet typischerweise schneller aus dem Kalten. Webpack bleibt mit Filesystem-Cache akzeptabel, sobald es warm ist.

Erstbesuch einer schweren Route

So testen Sie: Öffnen Sie eine abhängigkeitsschwere Route zum ersten Mal. Beobachten Sie DevTools → Network und Console auf Optimizer-Runs oder Request-Schübe.

Beobachtung: Vite kann gelegentlich Re-Optimizations und Reloads auslösen. Webpacks Vendor-Chunks machen Erstbesuche tendenziell gleichmäßiger.

Cross-Route-Navigation

So testen Sie: Navigieren Sie durch mehrere unberührte Routen und notieren Sie die Responsiveness, bis jede interaktiv ist.

Beobachtung: Vite verbessert sich nach den initialen Loads (da Module gecacht sind). Webpack bleibt über Routen hinweg konsistent vorhersagbar.

Linting-Einfluss

So testen Sie: Vergleichen Sie ESLint im Prozess mit einem separaten Prozess. Messen Sie die Tipp-Responsiveness und die HMR-Geschmeidigkeit.

Beobachtung: ESLint out-of-process hielt den Dev-Server responsiv und bewahrte in beiden Setups ein geschmeidiges HMR.

Ausgewogene Guidance – wann wir welches wählen würden

Wählen Sie Vite, wenn:

  • Cold Starts Ihren Workflow dominieren.
  • Ihr Modulgraph nicht riesig ist oder nicht in viele lazy Routes fragmentiert.
  • Plugins – insbesondere typed ESLint – sind leicht oder laufen out-of-process.

Wählen Sie Webpack + Fast Refresh, wenn:

  • Ihre App von eager Vendor-Pre-Bundling und vorhersagbarer First-Visit-Latenz über viele Routen profitiert.
  • Sie präzise Kontrolle über Loader/Plugins und Build-Output wünschen.
  • Sie Fast Refreshs State-Erhaltung und Overlays mögen.

Fazit

Sowohl Vite als auch Webpack sind exzellent. Angesichts der aktuellen Größe und Navigationsmuster von ilert-ui liefert webpack-dev-server + React Fast Refresh heute für uns die engste Feedback-Schleife – basierend auf qualitativer Developer-Experience, nicht auf Mikro-Benchmarks. Wir messen weiter, während sich unsere Codebase entwickelt, und werden Vite oder ein Framework erneut prüfen, wenn sich unsere Constraints ändern.

Blog-Beiträge, die dir gefallen könnten:

Sind Sie bereit, Ihr Incident-Management zu verbessern?

Start for free
Unsere Cookie-Richtlinie
Wir verwenden Cookies, um Ihre Erfahrung zu verbessern, den Seitenverkehr zu verbessern und für Marketingzwecke. Erfahren Sie mehr in unserem Datenschutzrichtlinie.
Open Preferences
Danke! Deine Einreichung ist eingegangen!
Hoppla! Beim Absenden des Formulars ist etwas schief gelaufen.
Danke! Deine Einreichung ist eingegangen!
Hoppla! Beim Absenden des Formulars ist etwas schief gelaufen.
Danke! Deine Einreichung ist eingegangen!
Hoppla! Beim Absenden des Formulars ist etwas schief gelaufen.