Home / Blog / Lazy Loading Individual Vue Components and Prefetching
Lazy Loading Individual Vue Components and Prefetching

Lazy Loading Individual Vue Components and Prefetching

Filip Rakowski
Filip Rakowski
Updated: December 8th 2024
This entry is part 3 of 6 in the series Vue.js Performance

Lazy loading in Vue is a powerful technique to improve performance by deferring the loading of components until they are needed. This approach significantly reduces initial page load time, enhancing the user experience and optimizing application performance.

In our previous article, we demonstrated how to lazy load Vue.js routes to achieve a smaller bundle size. While this technique is impactful, there’s even more potential to enhance performance by lazy loading individual Vue components and leveraging prefetching strategies.

In this guide, we’ll dive deep into lazy loading Vue components, explain how prefetching works, and share best practices for managing the download wait time to create seamless user experiences.

Which components should you load lazily?

The answer to this question is trivial and intuitive - everything that is not required on initial render.

This usually means everything that the user doesn’t see until he/she interacts with our app in some way. One of the most common examples are Vue components that we usually conditionally hide with v-if directives like modal windows or sidebars. Those components are usually good candidates to be loaded lazily.

Popup modal Vue component
Sidebar menu Vue component


It is important to understand exactly when a code-splitted Vue component is fetched. In the first part of this series you read that generally speaking we can assume that any lazily loaded resource is downloaded when it’s import() function is invoked and in case of Vue component that happens only when component is requested to render.

Let’s dig a little bit deeper. Take a look at below example:

<modal-window v-if="isModalVisible" />
<modal-window v-show="isModalVisible" />

Assuming isModalVisible variable is set to false even though both statements will take the same effect from the user point of view the v-if directive will remove component from the DOM completely while v-show will just add display: none CSS property.

An implication of that behavior is that with v-show component is always downloaded, no matter what’s the value inside it and for v-if directive component is downloaded only after it’s value will become true so remember that you should always use v-if with lazy loaded components!

Interesting side note: When you pass a component to components property of Vue instance under the hood it’s checking if passed value type is object or a function. If it’s a function the invocation happens only when component is requested to render. This is why the trick with v-if works.

So it turns out that all we need to do to lazily load any component is combining v-if directive and dynamic import statement like this:

<template>
  <div>
    <button @click="isModalVisible = true">Open modal</button>
    <modal-window v-if="isModalVisible" />
  </div>
</template>

<script>
export default {
  data () {
    return {
      isModalVisible: false
    }
  },
  components: {
    ModalWindow: () => import('./ModalWindow.vue')
  }
}
</script>

Impact on user experience

Lazy loading components can save a lot of time on initial download, but it comes with a price that the user has to pay later.

All those saved bytes are still needed for our application to work properly. Our users will need to download them at some point, which means they have to wait for the postponed resource to be downloaded. That can result in bad or extremely bad user experience, depending on how long time it takes to download the resource.

Such delay is much worse for components than whole pages because we’re not used to wait for individual parts of the UI to appear. When we click a button, we expect a reaction immediately. If nothing happens when we interact with a website, we either perceive the application as slow or broken, and most of us leave the site.

According to RAIL model guidelines every user input should generate a response in under 100ms.It is almost impossible to achieve such a low response time if we have to download resources before the application is able to respond. So what we can do?

It turns out that the solution is extremely simple. It’s called prefetching.

Prefetching

Prefetching in simple words, is just downloading certain resources that may be needed in a future before they are requested by the browser. In our case this might be a lazily loaded modal window. It also makes sense to prefetch lazily loaded routes. For example if we’re in a Category page of ecommerce shop we could prefetch Product page because there is a high chance user will visit it.

It’s important to note that the prefetching will only start after the browser has finished the initial load and becomes idle. Prefetching does not affect your page load or how fast your app becomes interactive (TTI).

Prefetching allows us to eliminate bad UX consequence of lazy loading without affecting page performance at all. How cool is this?

Interesting side note: If you’re using Nuxt.js every <nuxt-link> in currently opened route will prefetch it’s content automatically. You can read more about this behavior here.

To prefetch any resource (usually we prefetch the code-splitted ones like off-screen components or routes) the only thing that you need to do is put it into a <link rel="prefetch" href="url" /> tag. That’s all!

It’s not so easy when we use webpack that generates names of our bundles depending on order of module resolution (for example entry point becomes main.js, first code-splitted element encountered by webpack becomes 0.js, next one 1.js etc). Fortunately there is a feature that can help us easily prefetch any resource - magic comments.

Webpack’s magic comments are special phrases that are affecting build process when used in a comment. One of such magic comments is /* webpackPrefetch: true */. Simply put this comment inside dynamic import function like in the example below and your resource will be prefetched:

components: {
  ModalWindow: () => import(/* webpackPrefetch: true */ './ModalWindow.vue')
}

At the moment of code execution webpack will look for every magic comment like this and dynamically (which means at the code execution time, not in a build time) add <link rel="prefetch" url="resource-url" /> tag to <head> section of your application:

<link rel="prefetch" href="path-to-chunk-with-modal-window" />

Now when user will request chunk with ModalWindow.vue it will already be in a memory and appear instantly.

TIP: If you’re using Vue CLI 3 every lazily loaded resource is prefetched by default!

Important: Depending on your build configuration prefetching might work only on production mode.

Instead of magic comments you can use more general solution like webpack prefetch plugin or workbox.

Prefetching seems to solve all of our problems but browser support for this feature is not there yet for some of them. Especially lack of the Safari support can be painful.

Screenshot from caniuse.com showing no prefetching support in Safari browsers

Async components

Thankfully Vue has a very useful feature called async components that solves this problem gracefully.

Async component is basically an object containing:

  • dynamic component that will be loaded lazily represented by import() function
  • loading component that will be shown until dynamic component is loaded
  • error component that is shown only when dynamic component loading fails (in other words when Promise is rejected)

The second property is a great way to deal with browsers that don’t support prefetching while the third one let us deal with error handling in offline-first application in rare cases when the prefetching fails.

Async components are also wrapped in a function, like dynamic imports are. So they can be consumed in the same way any component is.

const ModalWindow = () => ({
  component: import('./ModalWindow.vue'),
  loading: LoadingComponent,
  error: ErrorComponent,
  // The error component will be displayed if a timeout is
  // provided and exceeded. Default: Infinity.
  timeout: 3000
})

export default {
  components: {
    ModalWindow
  }
}

This is how I made use of async components in Vue Storefront default theme:

Vue Async component showing lading indicator
Vue Async component showing connectivity error
Vue Async component showing generic error



TIP: You can display different error message if fetching fails due to a lack of network connectivity by using navigator.onLine property. The navigator object holds information about the browser.

Summary

In this article we digged really deep into lazy loading of individual components. Even though our users will need to download lazily loaded chunks at some point, which means they have to wait for the postponed resources to be downloaded we can download them before they’re requested with prefetching and in case it’s not supported by our browsers we can still deliver great waiting experience with async components.

What’s next

Now that you can call yourself master of lazy loaded components! It’s time to master another area of Vue performance! In the next part we will learn when it’s wise to lazy load third party libraries, how to identify which one of them are too big and how to easily find smaller equivalents.

Related Courses

Start learning Vue.js for free

Filip Rakowski
Filip Rakowski
Co-founder & CTO of Vue Storefront - Open Source eCommerce Platform for developers. I'm passionate about sharing my knowledge and thoughts on software architecture, web performance, Vue and raising awaraness about business impact of the technology and code we write. When I'm not working I'm probably playing with one of my cats.

Comments

Latest Vue School Articles

Why Vue.js is a Great Starting Point for New Coders

Why Vue.js is a Great Starting Point for New Coders

Dive into Vue.js as a beginner with our comprehensive guide. Learn why Vue is perfect for starting your web development journey, with insights into its community, learning resources, and real-world uses.
Eleftheria Batsou
Eleftheria Batsou
The Vue Form Component Pattern: Robust Forms Without the Fuss

The Vue Form Component Pattern: Robust Forms Without the Fuss

Learn to create easy to use Vue forms without a library and take advantage of native HTML validation.
Daniel Kelly
Daniel Kelly

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.