Memoisierung und Virtualisierung
Eine Vue-Anwendung, die mit 50 Einträgen flüssig läuft, kann mit 5.000 Einträgen zum Erliegen kommen. Virtualisierung, Memoisierung und gezieltes Profiling sind die drei Hebel, mit denen große Listen performant bleiben – messbar, nicht gefühlt.
Inhaltsverzeichnis
- 1. Messen vor Optimieren: Profiling-Workflow
- 2. Vue-Rendering verstehen: wann re-rendert was?
- 3. computed und watch: Memoisierung im reaktiven System
- 4. v-memo: gezieltes Einfrieren von Listenzeilen
- 5. shallowRef und shallowReactive: Reaktivität begrenzen
- 6. Virtualisierung mit vue-virtual-scroller
- 7. Code-Splitting und Lazy Loading von Routen und Komponenten
- 8. Debouncing und Throttling in reaktiven Systemen
- 9. Performance-Techniken im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Messen vor Optimieren: Profiling-Workflow
Die wichtigste Regel der Vue Performance-Optimierung: Messen, bevor optimiert wird. Vorzeitige Optimierung ohne Daten führt zu komplizierterem Code, ohne das tatsächliche Problem zu lösen. Der erste Schritt ist immer das Profiling – mit den Chrome DevTools Performance-Tab, dem Vue DevTools Component-Tree und Lighthouse. Erst wenn konkrete Messwerte zeigen, welche Komponente wie viel Zeit für Rendering benötigt, hat eine Optimierungsmaßnahme ein klares Ziel.
In den Chrome DevTools liefert der Performance-Tab mit aktiviertem Flame Chart eine detaillierte Ansicht, welche JavaScript-Funktionen wie viel Zeit kosten. Vue-Renders sind im Flame Chart erkennbar an Einträgen wie patch, updateComponent und renderWithContext. Die Vue DevTools zeigen im Timeline-Tab, welche Komponenten bei einer User-Interaktion re-rendern und wie lange jedes Rendering dauert. Ein Wert über 16ms pro Rendering bedeutet, dass der Frame-Rate-Schwellenwert von 60 fps unterschritten wird und der User sichtbares Ruckeln wahrnimmt.
Lighthouse im Audit-Modus misst Vue Performance aus Nutzerperspektive mit Core Web Vitals: Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS) und Interaction to Next Paint (INP). Diese Metriken sind direkt mit dem Google-Ranking verknüpft und geben konkrete Zielwerte vor. LCP unter 2,5 Sekunden, CLS unter 0,1 und INP unter 200ms sind die Schwellenwerte, ab denen Google eine Seite als "gut" bewertet. Die Optimierungsmaßnahmen in diesem Artikel adressieren direkt diese Metriken.
2. Vue-Rendering verstehen: wann re-rendert was?
Vue re-rendert eine Komponente, wenn sich eine reaktive Abhängigkeit ändert, die während des letzten Renderings gelesen wurde. Dieses System ist die Grundlage der Vue Performance-Optimierung: Wenn eine Komponente Reaktivität liest, die sie nicht für das Rendering benötigt, reagiert sie auf Änderungen, die sie gar nicht betreffen. Der klassische Fall: Eine Elternkomponente hält ein großes reaktives Objekt, eine Kindkomponente liest nur ein Feld daraus, ist aber als reaktive Abhängigkeit auf das gesamte Objekt eingetragen und re-rendert bei jeder Änderung des Objekts – auch wenn das gelesene Feld unverändert bleibt.
Das Verstehen des reaktiven Dependency-Tracking ist der Schlüssel zu effektiver Vue Performance-Optimierung. Vue 3 nutzt Proxy-basiertes Tracking: Jeder Zugriff auf eine reaktive Eigenschaft registriert die aktuelle Komponente als Subscriber. Wenn die Eigenschaft sich ändert, benachrichtigt Vue alle Subscriber und plant ein Re-Render. Wer reaktive Eigenschaften in Computed Properties isoliert, begrenzt die Subscriber-Menge: Nur die Computed Property beobachtet die breit abhängigen reaktiven Daten, und nur Komponenten, die die Computed Property lesen, werden bei Änderungen benachrichtigt.
3. computed und watch: Memoisierung im reaktiven System
Computed Properties sind das primäre Memoisierungsmittel in Vue für Vue Performance-Optimierungen. Eine Computed Property wird nur dann neu berechnet, wenn sich eine ihrer reaktiven Abhängigkeiten ändert. Zwischen Änderungen gibt sie das gecachte Ergebnis zurück, ohne die Berechnungslogik erneut auszuführen. Das ist besonders relevant für teure Transformationen großer Datensätze: Das Filtern, Sortieren und Transformieren einer Liste mit Tausenden von Einträgen wird nicht bei jedem Render der Komponente durchgeführt, sondern nur wenn sich die Quelldaten oder die Filterparameter ändern.
Der häufigste Vue Performance-Fehler bei computed Properties ist die versehentliche Nutzung von Methoden statt computed für Werte, die gecacht werden könnten. Eine Methode, die in einem Template-Ausdruck aufgerufen wird, führt ihre Berechnung bei jedem Render aus – kein Caching. Eine computed Property führt die Berechnung nur einmal aus und cached das Ergebnis. Bei komplexen Listen-Transformationen, Formatierungen und abgeleiteten Daten ist dieser Unterschied erheblich. Der zweite Fehler: Seiteneffekte in computed Properties einbauen. Computed Properties sollen pure Funktionen sein – Daten transformieren, nicht API-Calls auslösen oder State verändern. Seiteneffekte gehören in watch.
// Performance comparison: method vs. computed for expensive list transformation
// Use computed — cached between renders, recalculated only when dependencies change
import { ref, computed } from 'vue'
export function useProductList(rawProducts) {
const searchQuery = ref('')
const sortField = ref('name')
const sortDirection = ref('asc')
const selectedCategory = ref(null)
// GOOD: computed — runs once, cached until dependencies change
const filteredProducts = computed(() => {
const q = searchQuery.value.toLowerCase()
const cat = selectedCategory.value
return rawProducts.value
.filter(p => {
if (cat && p.category !== cat) return false
if (q && !p.name.toLowerCase().includes(q)) return false
return true
})
})
// Chain computeds — each layer only recalculates when its inputs change
const sortedProducts = computed(() => {
const field = sortField.value
const dir = sortDirection.value === 'asc' ? 1 : -1
return [...filteredProducts.value].sort((a, b) => {
if (a[field] < b[field]) return -1 * dir
if (a[field] > b[field]) return 1 * dir
return 0
})
})
// BAD PATTERN (do NOT do this — recalculates on every render):
// methods: { getFilteredProducts() { return rawProducts.filter(...) } }
return { searchQuery, sortField, sortDirection, selectedCategory, sortedProducts }
}
4. v-memo: gezieltes Einfrieren von Listenzeilen
v-memo ist ein Vue-3-Directive speziell für Vue Performance in großen Listen. Es friert das virtuelle DOM einer Komponente oder eines Elements ein, solange die angegebenen Abhängigkeiten unverändert bleiben. Wenn Vue eine Liste von Tausenden Elementen mit v-for rendert und die Eltern-Komponente re-rendert, prüft Vue normalerweise jedes Element, ob es aktualisiert werden muss. Mit v-memo="[item.id, item.selected]" überspringt Vue die Vnode-Erzeugung für alle Elemente, bei denen item.id und item.selected unverändert sind. Das spart erhebliche Rendering-Zeit bei Listen, bei denen nur wenige Elemente tatsächlich geändert werden.
Der typische Einsatzfall für v-memo im Kontext der Vue Performance-Optimierung: Eine Liste von Produkten, bei denen ein "ausgewählt"-Zustand getogglt werden kann. Ohne v-memo würde jede Auswahl-Änderung alle Listenelemente re-rendern. Mit v-memo="[item.id, item.isSelected]" werden nur die Zeilen neu gerendert, bei denen sich isSelected tatsächlich geändert hat. Bei 1.000 Einträgen, von denen 997 unverändert bleiben, ist das eine Verringerung des Rendering-Aufwands auf 0,3% des ursprünglichen Wertes. Die Kombination von v-memo mit v-for ist fast immer sinnvoll, wenn die Liste groß ist und Elemente selektiv ausgewählt oder markiert werden können.
5. shallowRef und shallowReactive: Reaktivität begrenzen
Der Standard ref() und reactive() in Vue macht alle verschachtelten Eigenschaften eines Objekts reaktiv – auch wenn nur die oberste Ebene des Objekts sich ändert und Reaktivität auf tieferen Ebenen nicht benötigt wird. Für große Datensätze wie Listen mit Hunderten von Objekten kann das zu erheblichem Overhead beim Erstellen der Proxy-Watcher führen. Vue Performance-Optimierung durch shallowRef und shallowReactive begrenzt die Reaktivität auf die oberste Ebene: Nur Änderungen am Ref selbst oder an direkten Eigenschaften des reaktiven Objekts lösen Re-Renders aus.
Ein typischer Einsatz von shallowRef für Vue Performance: Eine Liste von Produktobjekten, die als Ganzes aus der API kommt und als Ganzes ersetzt wird. Mit const products = shallowRef([])` und `products.value = await fetchProducts() löst nur das Ersetzen des gesamten Arrays ein Re-Render aus. Vue muss nicht jeden verschachtelten Wert jedes Produktobjekts auf Reaktivität überwachen. Das spart erheblichen Speicher und Setup-Zeit beim Laden großer Listen. Wenn einzelne Eigenschaften der Objekte reaktiv sein müssen, nutzt man triggerRef explizit nach Mutationen.
6. Virtualisierung mit vue-virtual-scroller
Virtualisierung ist der mächtigste Hebel für Vue Performance bei langen Listen: Statt alle Elemente ins DOM zu rendern, rendert ein virtueller Scroller nur die Elemente, die aktuell im sichtbaren Bereich des Containers liegen, plus einen kleinen Puffer darüber und darunter. Eine Liste mit 10.000 Einträgen rendert im DOM zu jedem Zeitpunkt vielleicht 30–50 Elemente. Das DOM-Overhead, der Speicherverbrauch und das initiale Rendering sind dadurch konstant – unabhängig von der Gesamtgröße der Liste.
vue-virtual-scroller ist die Standardbibliothek für Virtualisierung in Vue Performance-Projekten. Sie bietet drei Komponenten: RecycleScroller für Listen mit fixer Elementhöhe, DynamicScroller für Listen mit variabler Elementhöhe und DynamicScrollerItem als Wrapper für die Inhalte beim dynamischen Scroller. Der RecycleScroller ist der performanteste, weil er DOM-Elemente recycelt – beim Scrollen verschiebt er DOM-Knoten, statt sie zu löschen und neu zu erstellen. Das führt zu flüssigem Scrollen auch bei sehr schnellem Scrollen durch lange Listen.
<!-- ProductList.vue — Virtualized list with vue-virtual-scroller -->
<!-- Renders 10,000+ products with constant DOM size (~30 nodes visible) -->
<template>
<RecycleScroller
class="scroller h-screen overflow-y-auto"
:items="sortedProducts"
:item-size="120"
key-field="id"
v-slot="{ item }"
>
<!-- v-memo ensures rows only re-render when relevant data changes -->
<div v-memo="[item.id, item.isSelected, item.price]" class="product-row">
<ProductCard
:product="item"
:selected="item.isSelected"
@select="toggleSelect(item.id)"
/>
</div>
</RecycleScroller>
</template>
<script setup>
// Import only the component needed — tree-shaking removes unused components
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import ProductCard from './ProductCard.vue'
import { useProductList } from '@/composables/useProductList'
const { sortedProducts, toggleSelect } = useProductList()
</script>
7. Code-Splitting und Lazy Loading von Routen und Komponenten
Code-Splitting ist eine der effektivsten Vue Performance-Optimierungen für die initiale Ladezeit. Wenn eine Vite-Anwendung gebaut wird, erzeugt der Build ohne Code-Splitting ein einzelnes Bundle mit allen Komponenten, auch wenn die meisten davon beim initialen Seitenaufruf gar nicht benötigt werden. Lazy Loading mit dynamischem Import – () => import('./HeavyComponent.vue') – teilt die Anwendung in separate Chunks auf, die nur dann geladen werden, wenn sie tatsächlich benötigt werden.
In Vue Router werden Routen standardmäßig als Lazy-Load-Komponenten definiert: component: () => import('@/views/ProductDetail.vue'). Das bedeutet, dass das JavaScript für die Produktdetailseite erst geladen wird, wenn der User zu dieser Route navigiert. Für große Administrationsinterfaces oder feature-reiche Anwendungen kann das den initialen Bundle von mehreren Megabytes auf wenige Hundert Kilobyte reduzieren – mit direktem Einfluss auf LCP und Time-to-Interactive. Vue Performance durch Code-Splitting erfordert keine Änderung an der Komponenten-Logik, nur an den Import-Deklarationen.
8. Debouncing und Throttling in reaktiven Systemen
In Vue Performance-Optimierungen werden Debouncing und Throttling häufig vergessen, obwohl sie für user-initiierte Events wie Texteingabe, Scroll-Events und Resize-Events erhebliche Relevanz haben. Ein watch auf eine Sucheingabe, der bei jedem Tastendruck eine API-Anfrage auslöst, verschwendet Netzwerk-Ressourcen und kann die Anzeige mit veralteten Antworten überholen. Ein Debounce mit 300ms wartet, bis der User aufgehört hat zu tippen, bevor die Anfrage gesendet wird – das reduziert die Zahl der API-Anfragen in einem realistischen Tipp-Szenario um 90%.
Für Vue Performance bei Scroll- und Resize-Events ist Throttling die richtige Technik: Der Handler wird maximal einmal pro definierten Zeitraum aufgerufen, statt bei jedem Event-Firing. Ein Resize-Handler, der eine Vue-Komponente neu positioniert, muss nicht 60 mal pro Sekunde laufen – einmal pro 100ms reicht vollständig. VueUse (@vueuse/core) bietet fertige Composables für Debouncing (useDebounceFn), Throttling (useThrottleFn) und reaktive Debounce-Refs (refDebounced), die nahtlos in das reaktive System von Vue integriert sind.
9. Performance-Techniken im Vergleich
Die verschiedenen Vue Performance-Optimierungsstrategien haben unterschiedliche Einsatzbereiche und Aufwand-Nutzen-Profile. Die Wahl der richtigen Technik hängt davon ab, was das Profiling als Engpass identifiziert hat.
| Technik | Problem | Aufwand | Effekt |
|---|---|---|---|
| Virtualisierung | 10.000+ DOM-Knoten | Mittel | Sehr hoch — konstantes DOM |
| computed statt method | Teure Berechnungen im Template | Minimal | Hoch — Caching zwischen Renders |
| v-memo | Unnötiges Re-Rendering von Zeilen | Minimal | Hoch für selektive Updates |
| shallowRef | Zu viele Watcher bei großen Objekten | Gering | Mittel — weniger Proxy-Overhead |
| Code-Splitting | Hohes initiales Bundle | Gering | Sehr hoch — LCP, TTI verbessern |
Die Reihenfolge der Anwendung in der Praxis: Zuerst Code-Splitting und Lazy Loading, weil der Aufwand minimal ist und der Effekt auf die initiale Ladezeit maximal. Danach computed-Properties für teure Berechnungen absichern. Dann bei großen Listen Virtualisierung evaluieren. v-memo und shallowRef sind gezielte Werkzeuge für spezifische Vue Performance-Engpässe, die das Profiling aufgedeckt hat – nicht blindes Vorab-Optimieren.
Mironsoft
Vue Performance-Audit · Frontend-Optimierung · Core Web Vitals
Vue-Anwendung zu langsam? Wir messen und optimieren.
Wir analysieren Vue-Anwendungen mit Profiling-Tools, identifizieren konkrete Performance-Engpässe und implementieren gezielte Optimierungen mit messbarem Ergebnis.
Performance-Audit
Profiling mit Chrome DevTools und Vue DevTools – konkrete Engpässe identifizieren
Optimierung
Virtualisierung, Code-Splitting, Memoisierung – priorisiert nach messbarem Effekt
Core Web Vitals
LCP, CLS und INP auf Google-Zielwerte bringen – für Ranking und User Experience
10. Zusammenfassung
Vue Performance-Optimierungen sind nur dann wirkungsvoll, wenn sie auf gemessene Engpässe reagieren. Das Profiling mit Chrome DevTools und Vue DevTools zeigt, welche Komponenten wie viel Zeit für Rendering benötigen und welche Verbesserungen den größten Effekt haben. Computed Properties memoisieren teure Berechnungen zwischen Renders. v-memo verhindert unnötiges Re-Rendering von Listenzeilen, die sich nicht geändert haben. shallowRef begrenzt den Proxy-Overhead bei großen, flach gemuteten Datensätzen. Virtualisierung mit vue-virtual-scroller hält das DOM bei langen Listen konstant klein.
Code-Splitting ist der effektivste und aufwandsärmste Einstieg in Vue Performance-Optimierungen: Jede Route und jede große Komponente als Lazy Import macht das initiale Bundle schlanker und verbessert LCP und Time-to-Interactive direkt. Debouncing von Sucheingaben und Throttling von Scroll-Events verhindert unnötige API-Aufrufe und JavaScript-Berechnungen. Die Kombination dieser Techniken – angewandt auf die tatsächlichen Engpässe, die das Profiling aufgedeckt hat – ergibt eine Vue-Anwendung, die auch unter realen Bedingungen flüssig läuft.
Vue Performance — Das Wichtigste auf einen Blick
Messen zuerst
Chrome DevTools Performance-Tab + Vue DevTools Timeline zeigen, welche Komponente wie viel Zeit kostet. Profiling vor jeder Optimierung.
computed & v-memo
computed memoisiert teure Berechnungen. v-memo friert Listenzeilen ein, die sich nicht geändert haben – minimaler Code, großer Effekt.
Virtualisierung
vue-virtual-scroller rendert nur sichtbare Elemente – DOM-Größe bleibt bei 10.000+ Einträgen konstant, Scrollen bleibt flüssig.
Code-Splitting
Dynamische Imports für Routen und große Komponenten – initiales Bundle verkleinern, LCP und Time-to-Interactive direkt verbessern.