Vue is fast by default, but large lists, heavy components and careless props can still make apps sluggish.
Measure first: the Vue devtools performance timeline and the browser profiler show which components render often or slowly. Then apply the matching fix.
Load less: lazy-load routes and heavy components with defineAsyncComponent(() => import('./Chart.vue')); check bundle sizes; import only what you use from big libraries.
Update less:
- Props stability: a child re-renders when any prop it receives changes. Passing
:active-id="activeId"to every row re-renders all rows on each change; passing:active="item.id === activeId"re-renders only the two affected rows. - `v-once` renders a subtree once and skips it forever after.
- `v-memo="[item.selected]"` skips updating a subtree (often a
v-forrow) unless the listed values change. - Computed stability: since 3.4 computeds trigger only when their value changes, but returning a new object each time defeats that; compare manually if needed.
Track less: deep reactivity over huge arrays costs memory and time. Use shallowRef for large data you replace wholesale and markRaw for non-UI objects.
Render less: lists with thousands of rows should be virtualised (render only visible rows) with a library such as vue-virtual-scroller or TanStack Virtual. Avoid needless component abstraction in hot loops; each component instance has overhead.
Production builds strip dev warnings and are much faster than dev mode; always profile production.
Example
<script setup>
import { ref, shallowRef, defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const rows = shallowRef([]) // large list, replaced as a whole
const selectedId = ref(null)
const showChart = ref(false)
async function load() {
rows.value = await (await fetch('/api/rows')).json()
}
load()
</script>
<template>
<footer v-once>Build {{ new Date().getFullYear() }}</footer>
<div v-for="row in rows" :key="row.id"
v-memo="[row.id === selectedId]"
:class="{ selected: row.id === selectedId }"
@click="selectedId = row.id">
{{ row.label }}
</div>
<button @click="showChart = true">Show chart</button>
<HeavyChart v-if="showChart" :rows="rows" /> <!-- chunk loads on demand -->
</template>Remember
- Profile in production mode before optimising
- Keep props stable: pass derived booleans, not shared ids, to list rows
v-once,v-memo,shallowRef, async components and virtualisation cover most hot spots
Watch out for
- Using
v-memowith an incomplete dependency array, so rows don't update when other data changes.