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

Nico Meyer
Nico Meyer
Fullstack JavaScript Entwickler

Table of Contents

  1. Introduction
  2. Performance Profiling
  3. Page Load Optimization
  4. Update Optimizations
  5. General Optimizations

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.

ChildComponent.vue
<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 or v-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.

ChildComponent.vue
<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:

ChildComponent.vue
<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.