Code Splitting mit Vite und Rollup richtig konfigurieren
Ein einzelnes riesiges JavaScript Bundle bremst jede Vue App beim ersten Laden aus. Bundle Splitting mit dynamic import, manualChunks und Route basiertem Code Splitting verteilt den Code auf kleinere Häppchen, die der Browser parallel und bedarfsgerecht lädt.
Inhaltsverzeichnis
- 1. Warum Bundle Splitting bei Vue Apps zählt
- 2. Wie Vite und Rollup Chunks standardmäßig bilden
- 3. Dynamic Import: der Grundbaustein für Code Splitting
- 4. manualChunks: Vendor Code gezielt aufteilen
- 5. Route basiertes Splitting mit Vue Router
- 6. Component Level Splitting mit defineAsyncComponent
- 7. Chunk Größen analysieren mit dem Visualizer
- 8. Häufige Fehler beim Bundle Splitting
- 9. Splitting Strategien im direkten Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Bundle Splitting bei Vue Apps zählt
Bundle Splitting beschreibt die Aufteilung eines JavaScript Bundles in mehrere kleinere Dateien, die der Browser einzeln und bei Bedarf nachlädt, statt beim ersten Seitenaufruf alles auf einmal herunterzuladen. Ohne Bundle Splitting wächst eine Vue App mit jeder neuen Route, jeder neuen Bibliothek und jeder neuen Komponente zu einer einzigen Datei heran, die selbst für die Startseite komplett geladen und geparst werden muss, bevor überhaupt etwas Interaktives auf dem Bildschirm erscheint.
Der Effekt ist besonders auf mobilen Verbindungen und bei größeren Anwendungen spürbar: Ein Bundle von einem Megabyte unkomprimiert kann auf einer mittelmäßigen Mobilverbindung mehrere Sekunden zusätzliche Ladezeit bedeuten, bevor Vue überhaupt mit dem Hydration Prozess beginnen kann. Bundle Splitting adressiert genau dieses Problem, indem nur der Code geladen wird, den die aktuelle Route und die aktuell sichtbaren Komponenten tatsächlich benötigen. Der Rest wird in separate Chunks ausgelagert und bei Bedarf per dynamic import nachgeladen.
Vite nutzt unter der Haube Rollup für den Produktions Build und bringt dafür bereits sinnvolle Standardeinstellungen mit. Für die meisten Vue Apps reicht das nicht aus, sobald Drittanbieter Bibliotheken wie Chart Bibliotheken, Rich Text Editoren oder große Icon Sets ins Spiel kommen. Die folgenden Abschnitte zeigen, wie man Bundle Splitting für Vue Apps gezielt über die Vite Konfiguration, den Router und einzelne Komponenten steuert.
2. Wie Vite und Rollup Chunks standardmäßig bilden
Ohne zusätzliche Konfiguration erzeugt Rollup bereits automatisch getrennte Chunks für jeden dynamischen Import, den man im Code verwendet. Jede Route, die per () => import('./views/Dashboard.vue') statt eines statischen Imports eingebunden wird, landet in einem eigenen Chunk. Das nennt sich Automatic Chunking und ist der einfachste Einstieg in Bundle Splitting für Vue Apps, weil man keine zusätzliche Konfiguration braucht, sondern lediglich die Art des Imports ändert.
Problematisch wird es, wenn mehrere Routen dieselbe Drittanbieter Bibliothek importieren. Ohne explizite Steuerung dupliziert Rollup in manchen Konstellationen gemeinsam genutzten Code in mehrere Chunks, statt ihn einmal in einen gemeinsamen Chunk auszulagern. Das Ergebnis: Der Nutzer lädt denselben Bibliothekscode mehrfach herunter, sobald er zwischen Routen wechselt, weil jeder Routen Chunk seine eigene Kopie enthält. Genau hier setzt manualChunks an, das im nächsten Abschnitt behandelt wird.
// vite.config.js — baseline configuration, no manual chunking yet
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
// Rollup already splits dynamic imports into separate chunks by default
rollupOptions: {
output: {
// Chunk file naming, useful for cache busting and debugging
chunkFileNames: 'assets/js/[name]-[hash].js',
entryFileNames: 'assets/js/[name]-[hash].js'
}
}
}
})
3. Dynamic Import: der Grundbaustein für Code Splitting
Der import() Ausdruck ist die native JavaScript Syntax, auf der jedes Bundle Splitting in Vue Apps aufbaut. Anders als ein statischer import am Dateikopf gibt import() ein Promise zurück und wird von Rollup automatisch als Chunk Grenze erkannt. Vue Router, Pinia Stores und einzelne Komponenten lassen sich alle auf dieselbe Weise dynamisch laden, was Bundle Splitting zu einem konsistenten Muster über die gesamte Codebasis macht, statt zu einer Sonderlösung nur für Routen.
Ein wichtiger Aspekt beim dynamic import: Rollup respektiert Kommentare wie /* webpackChunkName: "..." */ nicht, das ist eine Webpack spezifische Syntax. Für benannte Chunks in Vite verwendet man stattdessen den Dateinamen selbst oder die manualChunks Funktion. Wer aus einer Webpack Migration kommt, sollte diese Kommentare entfernen, sie werden von Rollup schlicht ignoriert und haben keinerlei Effekt auf das Bundle Splitting.
// router/index.js — every route component is a separate dynamic import
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
component: () => import('../views/Home.vue')
},
{
path: '/dashboard',
// Heavy view with charts — only loaded when the user navigates here
component: () => import('../views/Dashboard.vue')
},
{
path: '/settings',
component: () => import('../views/Settings.vue')
}
]
export const router = createRouter({
history: createWebHistory(),
routes
})
// Same pattern works for conditional feature loading outside the router
async function loadExportFeature() {
const { generatePdfExport } = await import('../features/pdf-export.js')
return generatePdfExport
}
4. manualChunks: Vendor Code gezielt aufteilen
manualChunks in der Rollup Konfiguration erlaubt die explizite Kontrolle darüber, welche Module in welchen Chunk wandern. Statt sich auf die automatische Erkennung zu verlassen, definiert man eine Funktion, die für jedes verarbeitete Modul den Ziel Chunk Namen zurückgibt. Das häufigste Muster: Alle node_modules Abhängigkeiten in einen gemeinsamen vendor Chunk auslagern, damit Anwendungscode und Drittanbieter Code getrennt gecacht werden können. Ändert sich nur der Anwendungscode zwischen zwei Deployments, muss der Browser den Vendor Chunk nicht neu herunterladen.
Für größere Vue Apps lohnt sich eine feinere Aufteilung: Ein eigener Chunk für Vue selbst und Vue Router, ein separater Chunk für große, selten genutzte Bibliotheken wie PDF Generatoren oder Rich Text Editoren, und ein dritter Chunk für häufig genutzte, kleine Utility Bibliotheken. Diese granulare Bundle Splitting Strategie verhindert, dass eine einzelne große Bibliothek den gesamten Vendor Chunk aufbläht und dadurch das Caching für alle anderen Abhängigkeiten ineffizient macht, weil jede kleine Änderung an einer Bibliothek den kompletten Vendor Chunk invalidiert.
// vite.config.js — manual chunk strategy for vendor code separation
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('vue-router') || id.includes('/vue/')) {
return 'vendor-vue'
}
if (id.includes('chart.js') || id.includes('pdfmake')) {
// Heavy, rarely used libraries get their own chunk
return 'vendor-heavy'
}
// Everything else goes into a shared vendor chunk
return 'vendor'
}
}
}
},
chunkSizeWarningLimit: 500 // KB — warn earlier than the 500KB Rollup default suggests
}
})
5. Route basiertes Splitting mit Vue Router
Route basiertes Bundle Splitting ist der Bereich mit dem größten Effekt für die meisten Vue Apps, weil Nutzer selten alle Routen einer Anwendung in einer einzigen Sitzung besuchen. Jede Route als dynamischen Import zu deklarieren ist der erste Schritt, reicht aber nicht aus, wenn mehrere Routen dieselben schweren Abhängigkeiten teilen. Für diesen Fall lohnt sich eine Gruppierung verwandter Routen in denselben Chunk über gleichlautende Chunk Namen bei import(/* @vite-ignore */) Aufrufen oder über die manualChunks Funktion aus dem vorherigen Abschnitt.
Ein häufig übersehener Aspekt beim Route Splitting: Vue Router lädt den Chunk einer Route bereits beim Navigations Beginn, nicht erst nach Abschluss der Navigation. Das bedeutet, dass ein langsamer Netzwerkzugriff während der Navigation zu einer sichtbaren Verzögerung führt, wenn kein Ladeindikator eingebaut ist. Die Kombination aus router.beforeEach für einen globalen Ladebalken und Route basiertem Bundle Splitting sorgt für ein Verhalten, das schnell wirkt, selbst wenn der eigentliche Chunk noch lädt.
// router/index.js — loading indicator during route chunk fetch
import { createRouter, createWebHistory } from 'vue-router'
import NProgress from 'nprogress'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/reports', component: () => import('../views/Reports.vue') },
{ path: '/reports/export', component: () => import('../views/ReportsExport.vue') }
]
})
router.beforeEach(() => {
NProgress.start() // visible progress bar while the route chunk downloads
})
router.afterEach(() => {
NProgress.done()
})
export default router
6. Component Level Splitting mit defineAsyncComponent
Nicht jede Aufteilung muss auf Routenebene stattfinden. defineAsyncComponent erlaubt es, einzelne schwere Komponenten innerhalb einer Route erst dann zu laden, wenn sie tatsächlich gerendert werden. Ein typisches Beispiel: Ein Modal Dialog mit einem komplexen Formular, das nur bei einem Klick auf einen Button überhaupt sichtbar wird. Ohne Component Splitting lädt der Nutzer den Code für dieses Modal bereits beim ersten Aufruf der Route mit, selbst wenn er den Button nie klickt.
defineAsyncComponent unterstützt außerdem Optionen für Ladezustand, Fehlerzustand und Timeout, was besonders bei langsamen Verbindungen wichtig ist. Für Bundle Splitting bei Vue Apps ist diese Component Level Granularität die letzte Verfeinerungsstufe nach Route Splitting: Erst die großen Routen aufteilen, dann innerhalb der größten Routen die schwersten, selten genutzten Komponenten identifizieren und separat laden.
// components/ReportEditor.vue — lazy-loaded heavy component with states
import { defineAsyncComponent } from 'vue'
import LoadingSpinner from './LoadingSpinner.vue'
import ErrorFallback from './ErrorFallback.vue'
const RichTextEditor = defineAsyncComponent({
loader: () => import('./RichTextEditor.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorFallback,
delay: 200, // avoid spinner flash for fast connections
timeout: 8000 // fall back to error state after 8 seconds
})
export default {
components: { RichTextEditor },
data() {
return { showEditor: false }
}
}
7. Chunk Größen analysieren mit dem Visualizer
Bundle Splitting ohne Messung ist Rätselraten. Das Plugin rollup-plugin-visualizer erzeugt nach jedem Build eine interaktive Treemap Ansicht, die zeigt, wie groß jeder Chunk ist und welche Module ihn ausmachen. Für Bundle Splitting bei Vue Apps ist das der entscheidende Schritt, um herauszufinden, welche Bibliothek tatsächlich den größten Anteil am Bundle hat, statt basierend auf Vermutungen zu optimieren.
Ein typisches Ergebnis nach der ersten Analyse: Eine einzelne Icon Bibliothek, die komplett statt nur mit den benötigten Icons importiert wurde, macht plötzlich zwanzig Prozent des Hauptbundles aus. Oder eine Datumsbibliothek mit vollständiger Locale Unterstützung wird importiert, obwohl nur eine einzige Sprache benötigt wird. Diese Erkenntnisse lassen sich direkt in gezielte manualChunks Regeln oder Tree Shaking freundlichere Imports übersetzen.
// vite.config.js — chunk size visualization
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
vue(),
visualizer({
filename: './dist/stats.html',
gzipSize: true,
brotliSize: true,
template: 'treemap' // interactive treemap after every build
})
]
})
// Run: npm run build
// Then open dist/stats.html to inspect chunk composition
8. Häufige Fehler beim Bundle Splitting
Der häufigste Fehler ist übertriebenes Splitting: Wer jede noch so kleine Komponente in einen eigenen dynamischen Import verpackt, erzeugt hunderte winzige Chunks, deren HTTP Overhead die Ersparnis beim eigentlichen Dateigröße wieder auffrisst. Selbst mit HTTP/2 Multiplexing kostet jede zusätzliche Anfrage minimale, aber messbare Zeit für Verbindungsaufbau und Header Verarbeitung. Eine sinnvolle Faustregel: Chunks unter fünf Kilobyte lohnen sich selten als eigener Chunk, es sei denn, sie werden extrem selten geladen.
Ein zweiter häufiger Fehler betrifft zirkuläre Abhängigkeiten zwischen Chunks. Importiert Chunk A etwas aus Chunk B und umgekehrt, muss Rollup beide Chunks möglicherweise zusammenführen oder erzeugt eine ineffiziente Ladereihenfolge, bei der beide Chunks aufeinander warten. Das rollup-plugin-visualizer zeigt solche Verflechtungen häufig als überraschend große, gegenseitig abhängige Chunks. Ein dritter Fehler: manualChunks Regeln, die zu grob greifen und versehentlich Code in einen Chunk packen, der auf fast jeder Route benötigt wird, wodurch der vermeintliche Vorteil des Bundle Splittings verpufft, weil der Chunk ohnehin fast immer geladen werden muss.
9. Splitting Strategien im direkten Vergleich
Die folgende Übersicht ordnet die wichtigsten Bundle Splitting Strategien nach Granularität, typischem Ergebnis und geeignetem Einsatzfall für Vue Apps unterschiedlicher Größe.
| Strategie | Granularität | Effekt | Geeignet für |
|---|---|---|---|
| Kein Splitting | Ein Bundle | Langsame Erstladung | Sehr kleine Prototypen |
| Route Splitting | Pro Route | Deutlich kleinere Erstladung | Fast alle Mehrseiten Apps |
| manualChunks Vendor | Pro Abhängigkeitsgruppe | Besseres Langzeit Caching | Apps mit häufigen Deployments |
| Component Splitting | Pro schwerer Komponente | Kleinere Route Chunks | Modals, Editoren, Charts |
| Übertriebenes Splitting | Pro kleiner Komponente | HTTP Overhead überwiegt | Nicht empfohlen |
Die pragmatische Reihenfolge für Bundle Splitting bei Vue Apps: Zuerst Route Splitting einführen, dann mit dem Visualizer die größten Chunks identifizieren, anschließend gezielt manualChunks für Vendor Code und schließlich Component Level Splitting für einzelne schwere Komponenten innerhalb der größten Routen. Jeder Schritt sollte mit einer erneuten Visualizer Analyse überprüft werden, statt blind weitere Chunks zu erzeugen.
Mironsoft
Vite Build Optimierung und Bundle Splitting für Vue Apps
Vue App mit langsamer Erstladung durch riesiges Bundle?
Wir analysieren eure Chunk Struktur mit dem Visualizer, konfigurieren manualChunks gezielt und führen Route sowie Component Splitting ein, für messbar kürzere Ladezeiten auf mobilen Verbindungen.
Chunk Analyse
Treemap Analyse mit rollup-plugin-visualizer und konkrete Ansatzpunkte
Vite Konfiguration
manualChunks, Chunk Naming und Cache Strategien für schnellere Deployments
Route & Component Splitting
Dynamic Imports und defineAsyncComponent für schwere UI Bereiche
10. Zusammenfassung
Bundle Splitting für Vue Apps beginnt mit der einfachsten Maßnahme: Jede Route als dynamischen Import statt als statischen Import zu deklarieren. Vite und Rollup erzeugen daraus automatisch getrennte Chunks, ohne dass zusätzliche Konfiguration nötig ist. Für Vendor Code lohnt sich manualChunks, um Anwendungscode und Drittanbieter Code für effizienteres Caching zu trennen. defineAsyncComponent ergänzt das Bild auf Komponentenebene für schwere, selten genutzte UI Bereiche wie Modals und Rich Text Editoren.
Der entscheidende Schritt bei jedem Bundle Splitting Vorhaben ist die Messung mit dem rollup-plugin-visualizer. Ohne diese Analyse bleibt Bundle Splitting Rätselraten, mit ihr wird sichtbar, welche Bibliothek tatsächlich den größten Anteil am Bundle ausmacht und wo sich eine gezielte Aufteilung wirklich lohnt. Übertriebenes Splitting mit hunderten winzigen Chunks kehrt den Effekt um und sollte vermieden werden.
Bundle Splitting für Vue Apps — Das Wichtigste auf einen Blick
Route Splitting zuerst
Jede Route als dynamischen Import deklarieren, der größte Hebel mit dem geringsten Konfigurationsaufwand.
manualChunks für Vendor
Drittanbieter Code vom Anwendungscode trennen, für stabileres Browser Caching über Deployments hinweg.
Component Splitting
defineAsyncComponent für Modals, Editoren und Charts, die nicht bei jedem Seitenaufruf sichtbar sind.
Messen mit Visualizer
rollup-plugin-visualizer zeigt reale Chunk Größen, statt auf Vermutungen zu optimieren.