Vue Performance Optimization
Performance Techniques Overview
| Technique | What it does | When to use |
|---|---|---|
v-memo | Skip re-rendering subtree if deps unchanged | Large lists with complex items |
KeepAlive | Cache inactive components | Tab switching, route caching |
shallowRef | Only track top-level reactivity | Large objects where deep tracking is wasteful |
markRaw | Exclude object from reactivity | Third-party instances, large static data |
| Lazy loading | Load components on demand | Large components, routes |
| Virtual scrolling | Render only visible items | Lists with 1000+ items |
<template>
<div>
<!-- v-memo — skip re-render if deps unchanged -->
<!-- Only re-renders when item.id or selected changes -->
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.selected]"
>
<p>{{ item.name }}</p>
<span v-if="item.selected">✓ Selected</span>
</div>
<!-- KeepAlive — cache inactive components -->
<div class="tabs">
<button v-for="tab in tabs" :key="tab" @click="currentTab = tab">{{ tab }}</button>
</div>
<KeepAlive
:include="['HomeTab', 'ProfileTab']"
:exclude="['SettingsTab']"
:max="5"
>
<component :is="currentTabComponent" />
</KeepAlive>
</div>
</template>
<script setup>
import { ref, shallowRef, markRaw, reactive } from 'vue'
import HomeTab from './HomeTab.vue'
import ProfileTab from './ProfileTab.vue'
import SettingsTab from './SettingsTab.vue'
// shallowRef — only top-level reactivity (no deep tracking)
// Good for large objects where you replace the whole value
const bigData = shallowRef({ items: new Array(10000).fill(0) })
function updateData() {
// Must replace the whole object to trigger reactivity
bigData.value = { items: new Array(10000).fill(1) }
// bigData.value.items[0] = 1 // WON'T trigger update
}
// markRaw — exclude from reactivity system entirely
// Good for: third-party class instances, large static data, Map/Set
import { Chart } from 'chart.js'
const chartInstance = markRaw(new Chart(/* ... */))
// chartInstance won't be made reactive — saves memory
// Also useful for component references in reactive objects
const state = reactive({
// Without markRaw, Vue would try to make HomeTab reactive (wasteful)
currentComponent: markRaw(HomeTab),
})
const list = ref(Array.from({ length: 1000 }, (_, i) => ({
id: i, name: `Item ${i}`, selected: false
})))
const tabs = ['Home', 'Profile', 'Settings']
const currentTab = ref('Home')
const tabMap = { Home: HomeTab, Profile: ProfileTab, Settings: SettingsTab }
const currentTabComponent = computed(() => tabMap[currentTab.value])
</script>
<template>
<div>
<!-- Use v-show for frequent toggles -->
<div v-show="isVisible">Frequently toggled</div>
<!-- Use v-if for rare conditions -->
<HeavyComponent v-if="showHeavy" />
<!-- Lazy load heavy components -->
<Suspense>
<template #default><LazyChart /></template>
<template #fallback><div>Loading chart...</div></template>
</Suspense>
<!-- v-once — render once, never update -->
<footer v-once>
<p>© {{ year }} My App. All rights reserved.</p>
</footer>
</div>
</template>
<script setup>
import { ref, defineAsyncComponent } from 'vue'
// Lazy load heavy component
const LazyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const isVisible = ref(true)
const showHeavy = ref(false)
const year = new Date().getFullYear()
// Performance best practices:
// 1. Use computed for derived data (cached)
// 2. Use v-memo for expensive list items
// 3. Use KeepAlive for tab/route caching
// 4. Use shallowRef for large objects you replace wholesale
// 5. Use markRaw for non-reactive third-party instances
// 6. Lazy load routes and heavy components
// 7. Use virtual scrolling for 1000+ item lists (vue-virtual-scroller)
// 8. Avoid large reactive objects — use shallowReactive for flat data
// 9. Use v-once for truly static content
// 10. Profile with Vue DevTools before optimizing
</script>
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.