How to Improve the Performance of Vue Applications
Learn how to optimize the performance of your Vue applications. I give you detailed best practices to reduce load times and enhance user satisfaction.
September 7th, 2024
Table of Contents
Introduction
Vue is designed to be performant for most common use cases without significant need for manual optimizations. However, there are scenarios where additional fine-tuning is required.
Performance Profiling
Before we start optimizing, we need to measure performance. Here are some useful tools:
- PageSpeed Insights
- WebPageTest
- Chrome DevTools Performance Panel
- Vue DevTools Extension
Vue-specific performance markers can be enabled with app.config.performance
.
Page Load Optimization
Choosing the Right Architecture
For pages that need to load very quickly, consider Server-Side Rendering (SSR) or Static Site Generation (SSG). Pure client-side SPAs (Single Page Applications) often have slow load times. Marketing pages (e.g., landing pages) can be separately served as static HTML.
Bundle Size Reduction and Tree-shaking
One of the most effective ways to optimize load speed is reducing the JavaScript bundle size:
- Use modern build tools that support "tree-shaking".
- Be mindful of the size of new dependencies.
- Use ES modules when possible.
// Example of tree-shaking with dynamic import
import { defineAsyncComponent } from 'vue';
const Foo = defineAsyncComponent(() => import('./Foo.vue'));
Code Splitting
Code splitting involves dividing the application bundle into smaller chunks that can be loaded on demand. This allows for faster initial page load speeds.
function loadLazy() {
return import('./lazy.js');
}
Update Optimizations
Props Stability
A child component only updates when at least one of its props has changed. It is important to keep props stable to avoid unnecessary updates.
<template>
<div :class="{ active: isActive }">...</div>
</template>
<script lang="ts" setup>
defineProps<{ id: number, active: boolean }>();
</script>
Using v-once
and v-memo
v-once
: Can be used to render content that relies on runtime data but never needs to update.v-memo
: Can conditionally skip updates for large sub-trees orv-for
lists.
<template>
<div v-once>
{{ neverChangingContent }}
</div>
</template>
Computed Stability
Starting from Vue 3.4, a computed property will only trigger effects when its value changes.
const count = ref(0);
const isEven = computed(() => count.value % 2 === 0);
General Optimizations
Virtualizing Large Lists
Large lists should be virtualized to improve performance. Only the items close to the viewport are rendered.
Available libraries:
vue-virtual-scroller
vue-virtual-scroll-grid
vueuc/VVirtualList
Reducing Reactivity Overhead for Large Immutable Structures
Vue's reactivity system is deep by default. For large, deeply nested arrays, this can cause performance overhead. Use shallowRef()
and shallowReactive()
to maintain reactivity only at the root level.
const shallowArray = shallowRef([/* large list of deep objects */]);
Avoiding Unnecessary Component Abstractions
Sometimes you create renderless components or higher-order components for better abstraction or code organization. Renderless components are those that do not render their own DOM elements but provide functions and behavior to their child components. Higher-order components are components that render other components with additional props.
While these patterns are useful and often necessary, keep in mind that component instances are more expensive than plain DOM nodes. Each component instance carries overhead in terms of memory usage and performance.
Overusing these patterns, especially in performance-critical areas like large lists, can result in noticeable performance costs in terms of longer render times and increased memory consumption.
Example and Context:
Imagine a list with 100 items, where each item has its own component instance and multiple child components.
<template>
<div v-for="item in items" :key="item.id">
<ItemComponent :item="item" />
</div>
</template>
<script lang="ts" setup>
const items = ref([...]);
</script>
In this situation, hundreds of component instances are created, each with its own internal state and lifecycle hooks.
By removing unnecessary component abstractions, you can significantly reduce the number of these instances:
<template>
<div v-for="item in items" :key="item.id">
<div :class="{ active: item.isActive }">{{ item.name }}</div>
</div>
</template>
<script lang="ts" setup>
const items = ref([...]);
</script>
When is Optimization Necessary?
It's important to note that removing only a few component instances won't have a noticeable impact. If a component is rendered only a few times in the entire application, the performance costs are negligible.
The best scenario to consider this optimization is in large lists or heavily used areas of the application. For instance, in a list of 100 items where each item contains multiple child components, removing a single unnecessary abstraction component could result in the reduction of hundreds of component instances.
The goal is to strike the right balance between code organization and performance, recognizing when abstraction is costing more than it's worth in terms of performance.