Optimize the Performance of a Vue App with Async Components

Share this article

Optimize the Performance of a Vue App with Async Components

Key Takeaways

  • Asynchronous components and webpack’s code-splitting functionality can be used in a Vue application to load parts of the page after the app’s initial render, reducing initial load time and improving app performance.
  • Vue caters for dynamic imports, a function-like form of import that returns a Promise containing the requested (Vue) component, enabling modules to be loaded using expressions.
  • Asynchronous components can be conditionally loaded, only loading them when they’re really needed, which can be achieved by setting a parameter to be true when a user performs a specific action.
  • It’s possible to define a loading and/or error component for when the async component takes some time to load or fails to load, which can be useful to show a loading animation or error message.

Want to learn Vue.js from the ground up? Get an entire collection of Vue books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $14.99/month.

Single-page applications sometimes cop a little flack for their slow initial load. This is because traditionally, the server will send a large bundle of JavaScript to the client, which must be downloaded and parsed before anything is displayed on the screen. As you can imagine, as your app grows in size, this can become more and more problematic.

Luckily, when building a Vue application using Vue CLI (which uses webpack under the hood), there are a number of measures one can take to counteract this. In this article, I’ll demonstrate how make use of both asynchronous components and webpack’s code-splitting functionality to load in parts of the page after the app’s initial render. This will keep the initial load time to a minimum and give your app a snappier feel.

To follow this tutorial, you need a basic understanding of Vue.js and optionally Node.js.

Async Components

Before we dive into creating asynchronous components, let’s take a look at how we normally load a component. To do so, we’ll use a very simple message component:

<!-- Message.vue -->
<template>
  <h1>New message!</h1>
</template>

Now that we’ve created our component, let’s load it into our App.vue file and display it. We can just import the component and add it to the components option so we can use it in our template:

<!-- App.vue -->
<template>
  <div>
    <message></message>
  </div>
</template>

<script>
import Message from "./Message";
export default {
  components: {
    Message
  }
};
</script>

But what happens now? The Message component will be loaded whenever the application is loaded, so it’s included in the initial load.

This might not sound like a huge problem for a simple app, but consider something more complex like a web store. Imagine that a user adds items to a basket, then wants to check out, so clicks the checkout button which renders a box with all details of the selected items. Using the above method, this checkout box will be included in the initial bundle, although we only need the component when the user clicks the checkout button. It’s even possible that the user navigates through the website without ever clicking the checkout button, meaning that it doesn’t make sense to waste resources on loading this potentially unused component.

To improve the efficiency of the application, we can combine both lazy loading and code splitting techniques.

Lazy loading is all about delaying the initial load of a component. You can see lazy loading in action on sites like medium.com, where the images are loaded in just before they’re required. This is useful, as we don’t have to waste resources loading all the images for a particular post up front, as the reader might skip the article halfway down.

The code splitting feature webpack provides allows you to split your code into various bundles that can then be loaded on demand or in parallel at a later point in time. It can be used to load specific pieces of code only when they’re required or used.

Dynamic Imports

Luckily, Vue caters for this scenario using something called dynamic imports. This feature introduces a new function-like form of import that will return a Promise containing the requested (Vue) component. As the import is a function receiving a string, we can do powerful things like loading modules using expressions. Dynamic imports have been available in Chrome since version 61. More information about them can be found on the Google Developers website.

The code splitting is taken care of by bundlers like webpack, Rollup or Parcel, which understand the dynamic import syntax and create a separate file for each dynamically imported module. We’ll see this later on in our console’s network tab. But first, let’s take a look at the difference between a static and dynamic import:

// static import
import Message from "./Message";

// dynamic import
import("./Message").then(Message => {
  // Message module is available here...
});

Now, let’s apply this knowledge to our Message component, and we’ll get an App.vue component that looks like this:

<!-- App.vue -->
<template>
  <div>
    <message></message>
  </div>
</template>

<script>
import Message from "./Message";
export default {
  components: {
    Message: () => import("./Message")
  }
};
</script>

As you can see, the import() function will resolve a Promise that returns the component, meaning that we’ve successfully loaded our component asynchronously. If you take a look in your devtools’ network tab, you’ll notice a file called 0.js that contains your asynchronous component.

Code splitting webpack

Conditionally Loading Async Components

Now that we have a handle on asynchronous components, let’s truly harvest their power by only loading them when they’re really needed. In the previous section of this article, I explained the use case of a checkout box that’s only loaded when the user hits the checkout button. Let’s build that out.

Project Setup

If you don’t have Vue CLI installed, you should grab that now:

npm i -g @vue/cli

Next, use the CLI to create a new project, selecting the default preset when prompted:

vue create my-store

Change into the project directory, then install the ant-design-vue library, which we’ll be using for styling:

cd my-store
npm i ant-design-vue

Next, import the Ant Design library in src/main.js:

import 'ant-design-vue/dist/antd.css'

Finally, create two new components in src/comonents, Checkout.vue and Items.vue:

touch src/components/{Checkout.vue,Items.vue}

Making the Store View

Open up src/App.vue and replace the code there with the following:

<template>
  <div id="app">
    <h1>{{ msg }}</h1>
    <items></items>
  </div>
</template>

<script>
import items from "./components/Items"

export default {
  components: {
    items
  },
  name: 'app',
  data () {
    return {
      msg: 'My Fancy T-Shirt Store'
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

h1, h2 {
  font-weight: normal;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: inline-block;
  margin: 0 10px;
}

a {
  color: #42b983;
}
</style>

There’s nothing fancy going on here. All we’re doing is displaying a message and rendering an <items> component.

Next, open up src/components/Items.vue and add the following code:

<template>
  <div>
    <div style="padding: 20px;">
      <Row :gutter="16">
        <Col :span="24" style="padding:5px">
          <Icon type="shopping-cart" style="margin-right:5px"/>{{shoppingList.length}} item(s)
          <Button @click="show = true" id="checkout">Checkout</Button>
        </Col>
      </Row>
    </div>
    <div v-if="show">
      <Row :gutter="16" style="margin:0 400px 50px 400px">
        <checkout v-bind:shoppingList="shoppingList"></checkout>
      </Row>
    </div>
    <div style="background-color: #ececec; padding: 20px;">
      <Row :gutter="16">
        <Col :span="6" v-for="(item, key) in items" v-bind:key="key" style="padding:5px">
          <Card v-bind:title="item.msg" v-bind:key="key">
            <Button type="primary" @click="addItem(key)">Buy ${{item.price}}</Button>
          </Card>
        </Col>
      </Row>
    </div>
  </div>
</template>

<script>
import { Card, Col, Row, Button, Icon } from 'ant-design-vue';

export default {
  methods: {
    addItem (key) {
      if(!this.shoppingList.includes(key)) {
        this.shoppingList.push(key);
      }
    }
  },
  components: {
    Card, Col, Row, Button, Icon,
    checkout: () => import('./Checkout')
  },
  data: () => ({
    items: [
      { msg: 'First Product', price: 9.99 },
      { msg: 'Second Product', price: 19.99 },
      { msg: 'Third Product', price: 15.00 },
      { msg: 'Fancy Shirt', price: 137.00 },
      { msg: 'More Fancy', price: 109.99 },
      { msg: 'Extreme', price: 3.00 },
      { msg: 'Super Shirt', price: 109.99 },
      { msg: 'Epic Shirt', price: 3.00 },
    ],
    shoppingList: [],
    show: false
  })
}
</script>
<style>
#checkout {
  background-color:#e55242;
  color:white;
  margin-left: 10px;
}
</style>

In this file, we’re displaying a shopping cart icon with the current amount of purchased items. The items themselves are pulled from an items array, declared as a data property. If you click on an item’s Buy button, the addItem method is called, which will push the item in question to a shoppingList array. In turn, this will increment the cart’s total.

We’ve also added a Checkout button to the page, and this is where things start to get interesting:

<Button @click="show = true" id="checkout">Checkout</Button>

When a user clicks on this button, we’re setting a parameter show to be true. This true value is very important for the purpose of conditionally loading our async component.

A few lines below, you can find a v-if statement, which only displays the content of the <div> when show is set to true. This <div> tag contains the checkout component, which we only want to load when the user has hit the checkout button.

The checkout component is loaded asynchronously in the components option in the <script> section. The cool thing here is that we can even pass arguments to the component via the v-bind statement. As you can see, it’s relatively easy to create conditional asynchronous components:

<div v-if="show">
  <checkout v-bind:shoppingList="shoppingList"></checkout>
</div>

Let’s quickly add the code for the Checkout component in src/components/Checkout.vue:

<template>
  <Card title="Checkout Items" key="checkout">
    <p v-for="(k, i) in this.shoppingList" :key="i">
      Item: {{items[Number(k)].msg}} for ${{items[Number(k)].price}}
    </p>
  </Card>
</template>

<script>
import { Card } from 'ant-design-vue';

export default {
  props: ['shoppingList'],
  components: {
    Card
  },
  data: () => ({
    items: [
      { msg: 'First Product', price: 9.99 },
      { msg: 'Second Product', price: 19.99 },
      { msg: 'Third Product', price: 15.00 },
      { msg: 'Fancy Shirt', price: 137.00 },
      { msg: 'More Fancy', price: 109.99 },
      { msg: 'Extreme', price: 3.00 },
      { msg: 'Super Shirt', price: 109.99 },
      { msg: 'Epic Shirt', price: 3.00 },
    ]
  })
}
</script>

Here we’re looping over the props we receive as shoppingList and outputting them to the screen.

You can run the app using the npm run serve command. Then navigate to http://localhost:8080/. If all has gone according to plan, you should see something like what’s shown in the image below.

Vue Webshop Demo

Try clicking around the store with your network tab open to assure yourself that the Checkout component is only loaded when you click the Checkout button.

You can also find the code for this demo on GitHub.

Async with Loading and Error Component

It’s even possible to define a loading and/or error component for when the async component takes some time to load or fails to load. It can be useful to show a loading animation, but bear in mind this again slows down your application. An asynchronous component should be small and fast to load. Here’s an example:

const Message = () => ({
  component: import("./Message"),
  loading: LoadingAnimation,
  error: ErrorComponent
});

Conclusion

Creating and implementing asynchronous components is very easy and should be part of your standard development routine. From a UX perspective, it’s important to reduce the initial load time as much as possible to maintain the user’s attention. Hopefully this tutorial has assisted you with loading your own components asynchronously and applying conditions to them to delay (lazy load) the load of the component.

Frequently Asked Questions on Vue Async Components

What are the benefits of using Vue Async Components?

Vue Async Components are a powerful feature in Vue.js that allows developers to load components asynchronously. This means that the component is only loaded when it’s needed, which can significantly improve the performance of your application. This is particularly beneficial for large applications where loading all components at once can slow down the application. By loading components asynchronously, you can ensure that your application remains fast and responsive, providing a better user experience.

How do I implement Vue Async Components in my application?

Implementing Vue Async Components in your application is relatively straightforward. You can use the Vue.component method and return a Promise in the factory function. The Promise should resolve with the component definition. Here’s a basic example:

Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// Pass the component definition to the resolve callback
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})
In this example, the component is loaded asynchronously after a delay of 1 second.

Can I use Vue Async Components with Vue Router?

Yes, you can use Vue Async Components with Vue Router. In fact, Vue Router has built-in support for component splitting and asynchronous loading. When defining your routes, instead of directly providing the component, you can provide a function that returns a Promise that resolves to the component. This allows you to load components only when they are needed, improving the performance of your application.

What is the difference between Vue Async Components and regular Vue Components?

The main difference between Vue Async Components and regular Vue Components is how they are loaded. Regular Vue Components are loaded synchronously, meaning they are loaded all at once when the application starts. On the other hand, Vue Async Components are loaded asynchronously, meaning they are loaded only when they are needed. This can significantly improve the performance of your application, especially for large applications with many components.

How can I handle errors when using Vue Async Components?

When using Vue Async Components, you can handle errors by providing a second function to the Vue.component method. This function will be called if the Promise is rejected. Here’s an example:

Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// Pass the component definition to the resolve callback
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
}, function (reason) {
console.error(reason)
})
In this example, if the Promise is rejected, the error will be logged to the console.

Can I use Vue Async Components with Vuex?

Yes, you can use Vue Async Components with Vuex. Vuex is a state management library for Vue.js, and it works well with Vue Async Components. You can dispatch actions and commit mutations from your async components just like you would with regular components.

How can I test Vue Async Components?

Testing Vue Async Components is similar to testing regular Vue Components. You can use libraries like Vue Test Utils and Jest to write unit tests for your components. However, since async components are loaded asynchronously, you may need to use async/await in your tests to wait for the component to be loaded before performing assertions.

Can I use Vue Async Components with Vue CLI?

Yes, you can use Vue Async Components with Vue CLI. Vue CLI is a command-line tool for scaffolding Vue.js projects, and it has built-in support for async components. You can use the Vue.component method to define async components in your Vue CLI project.

How can I optimize the performance of Vue Async Components?

There are several ways to optimize the performance of Vue Async Components. One way is to use code splitting, which allows you to split your code into smaller chunks that can be loaded on demand. Another way is to use lazy loading, which allows you to load components only when they are needed. You can also use prefetching and preloading to load components in the background when the browser is idle.

Can I use Vue Async Components with Nuxt.js?

Yes, you can use Vue Async Components with Nuxt.js. Nuxt.js is a framework for building Vue.js applications, and it has built-in support for async components. You can define async components in your Nuxt.js project using the Vue.component method.

Michiel MuldersMichiel Mulders
View Author

Fullstack Blockchain Developer at TheLedger.be with a passion for the crypto atmosphere.

Asyncasync componentslearn-vuevuevue-hubvue.js
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week