<v/>
{ }
Vue.js · Performance · Virtualisierung · Memoisierung
Vue Performance: Große Listen,
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.

17 Min. Lesezeit v-memo · computed · vue-virtual-scroller · Code-Splitting · Profiling Vue 3 · Vite · Chrome DevTools · Vue DevTools

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.

11. FAQ: Vue Performance

1Wie erkenne ich, welche Komponente das Problem verursacht?
Vue DevTools → Timeline → Interaktion ausführen. Zeigt Komponenten und Rendering-Dauer. Werte über 16ms verursachen sichtbares Ruckeln.
2Ab wann vue-virtual-scroller einsetzen?
Ab 200–500 Elementen evaluieren, ab 1.000+ empfohlen. Integration ist gering aufwendig, Performance-Gewinn erheblich.
3v-memo vs. shouldComponentUpdate?
v-memo wirkt feingranularer – auf einzelne Elemente innerhalb einer Komponente. Überspringt VDOM-Diffing für angegebene Abhängigkeiten.
4Wann bringt computed nichts?
Bei trivialen Berechnungen ist Caching-Overhead größer als Ersparnis. computed lohnt sich für Array-Filterungen, Sortierungen und komplexe Transformationen.
5shallowRef richtig einsetzen?
Für große Arrays/Objekte, die als Ganzes ersetzt werden (API-Antworten). triggerRef nach Mutationen aufrufen, wenn verschachtelte Reaktivität nötig ist.
6refDebounced vs. debounced watch?
refDebounced für Template-Bindings (Sucheingabe). Debounced watch für Seiteneffekte (API-Call). Beide aus VueUse.
7Code-Splitting verbessert LCP?
Kleineres initiales Bundle → weniger JS-Parse-Zeit → Browser rendert größten sichtbaren Inhalt früher. Direkte LCP-Verbesserung.
8Unbeabsichtigte Re-Renders finden?
Vue DevTools → Gear-Icon → Highlight component re-renders. Rot gefärbte Komponenten re-rendern. Ursache oft zu breite reaktive Abhängigkeiten.
9Beeinflusst Virtualisierung SEO?
Für SEO-kritische Listen: SSR mit Nuxt. Browser übernimmt Virtualisierung nach Hydration. Für dynamische Apps ohne SEO-Relevanz reicht clientseitige Virtualisierung.
10Welches Core Web Vital profitiert am meisten?
INP reagiert direkt auf JS-Performance: Virtualisierung, v-memo, Debouncing reduzieren Haupt-Thread-Blockierung. Code-Splitting verbessert LCP.