Bundle Splitting für Vue Apps mit Vite und Rollup
AI generated
<v/>
{ }
Vue.js · Vite · Rollup · Performance
Bundle Splitting für Vue Apps
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.

19 Min. Lesezeit dynamic import · manualChunks · rollup-plugin-visualizer Vite 5+ · Vue Router 4

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.

11. FAQ: Bundle Splitting für Vue Apps

1Bundle Splitting vs. Code Splitting?
Meist synonym. Code Splitting ist die Technik im Code, Bundle Splitting das Ergebnis: mehrere kleinere Bundles.
2Reicht dynamic import allein?
Für Route Splitting ja. Für Caching von Vendor Code über Deployments hinweg zusätzlich manualChunks nutzen.
3Wie viele Chunks sind zu viele?
Keine feste Zahl, aber Chunks unter fünf Kilobyte lohnen sich selten wegen des HTTP Overheads.
4Funktioniert webpackChunkName in Vite?
Nein, wird von Rollup ignoriert. Stattdessen manualChunks Funktion oder Dateinamen nutzen.
5Wie finde ich aufblähende Bibliotheken?
Mit rollup-plugin-visualizer eine Treemap erzeugen, sie zeigt sofort den Anteil jedes Moduls.
6Jede Komponente per defineAsyncComponent laden?
Nein, nur schwere, selten sichtbare Komponenten. Kleine Komponenten profitieren kaum und erzeugen Overhead.
7Was passiert bei zirkulären Chunk-Abhängigkeiten?
Rollup führt Chunks möglicherweise zusammen oder erzeugt eine ineffiziente Ladereihenfolge. Der Visualizer zeigt solche Fälle deutlich.
8Verbessert manualChunks die initiale Ladezeit?
Vor allem verbessert es Caching zwischen Deployments, indem sich der Vendor Chunk seltener ändert.
9Wann lädt Vue Router den Chunk?
Beim Navigationsbeginn, nicht danach. Ein globaler Ladeindikator verhindert eine unsichtbare Verzögerung.
10Muss ich Bundle Splitting immer einsetzen?
Bei sehr kleinen Prototypen gering, sobald mehrere Routen und Bibliotheken dazukommen, lohnt sich zumindest Route Splitting fast immer.