Async Vue.js Components

As your application grows, you start to look for performance patterns to make it faster. On the way, you’ll find code splitting and lazy loading to be two of them that make your initial bundle smaller by deferring the loading of code chunks until needed.

Lazy loading makes a lot of sense to be applied to the app routes and has a great impact because each route is a different section of an app.

Another case where lazy loading makes sense is when you have components which rendering is deferred. These components can be tooltips, popovers, modals, etc, and can be used as async components.

Let’s see how to build and lazy load these async components in Vue.

Lazy Loading a Component

Before we start by lazy loading a component, let’s first remember how we usually load a component. For that, let’s create a Tooltip.vue component:

<!-- Tooltip.vue -->
<template>
  <h2>Hi from Tooltip!</h2>
</template>

Nothing special here, it’s just a simple component. We can use it in another component by doing local registration, importing the Tooltip component, and adding it to the components component option. For instance, in an App.vue component:

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

<script>
import Tooltip from "./Tooltip";

export default {
  components: {
    Tooltip
  }
};
</script>

The Tooltip component is imported, used and loaded as long as the App is imported, probably on the initial load. But think: wouldn’t make sense to load that component only when we’re going to use it? It’s likely that the user navigates through the whole sit without the need of a tooltip.

Why should we spend precious resources on loading the component at the beginning of the application? We can apply a combination of lazy loading and code splitting in order to improve that. Lazy loading is the technique of loading something at a later phase when it’s going to be used.

While code splitting is about separating a piece of code in a separate file, known as chunk, so that the initial bundle of your application gets reduced, increasing the initial load.

Vue makes it easy to apply these techniques by using the language standard dynamic import, a JavaScript feature likely landing on the ES2018 version of the language that allows loading a module in runtime. We’ll have a separate article to dive deep into these concepts, but let’s see it from a practical and simple perspective.

Modern bundlers, such as Webpack (since version 2), Rollup and Parcel will understand this syntax and automatically create a separate file for that module which will load when it’s required.

I can imagine you’re already familiar with importing a module the usual static way. However the dynamic import is a function that returns a promise, containing the module as its payload. The following example shows how to import the utils module, both in a static and lazy-loaded dynamic way:

// static import
import utils from "./utils";

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

Lazy loading a component in Vue is as easy as importing the component using dynamic import wrapped in a function. In the previous example, we can lazy load the Tooltip component as follows:

export default {
  components: {
    Tooltip: () => import("./Tooltip")
  }
};

That’s it, just by passing () => import("./tooltip") instead of the previous import Tooltip from "./tooltip" Vue.js will lazy load that component as soon as is requested to be rendered.

Not only that, but it will apply code splitting as well. You can test that by running that code using any of the bundlers mentioned. An easy way is by using the vue-cli, but at the end of the article, you’ll find an already built demo. Once running, open the dev tools and you’ll see a JavaScript file with a name like 1.chunk.js:

Conditionally Loading an Async Component

Master Vue.js with Vue School

Top notch Vue.js courses and over 200 lessons for just $12 per month!

In the previous example, even though we’re lazily loading the Tooltip component, it’ll be loaded as soon as it is required to be rendered, which happens right away when the App component gets mounted.

However, in practice, we’d like to defer the Tooltip component loading until it is required, which usually happens conditionally after a certain event has been triggered, for example when hovering a button or text.

For simplicity, let’s take the previous App component and add a button to make the Tooltip render conditionally using a v-if condition:

<!-- App.vue -->
<template>
  <div>
    <button @click="show = true">Load Tooltip</button>
    <div v-if="show">
      <tooltip></tooltip>
    </div>
  </div>
</template>

<script>
export default {
  data: () => ({ show: false }),
  components: {
    Tooltip: () => import("./Tooltip")
  }
};
</script>

Keep in mind that Vue doesn’t use a component until it needs to be rendered. Meaning that the component will not be required until that point and that’s when the component will be lazy loaded.
s
You can see a demo of this example running in this Codesandbox. Keep in mind that Codesandbox doesn’t do code splitting, so if you want to check that in the dev tools you can download the demo and run it locally on your machine.

User Experience on Async Components

Most of the times, async components load quite fast since they’re small pieces of code chunks stripped out from the main bundle. But imagine you’re lazy loading a big modal component under a very slow connection. That could probably take some seconds to get loaded and rendered.

Sure you can use some optimizations like HTTP caching or resource hints to load it in memory with low priority beforehand, in fact, the new vue-cli applies prefetch to these lazily loaded chunks. Still, in a few cases, it can take some time to load.

From the UX point of view, if a task takes more than 1 second to happen, you start losing user’s attention.

However, the attention could be kept by providing feedback to the user. There are several progress indicator components we could use while loading, in order to engage user’s attention, but how could we use a nice spinner or progress bar while an async component is loading?

Loading Component

Do you remember we’ve used a function with the dynamic import to lazy load an async component?

export default {
  components: {
    Tooltip: () => import("./Tooltip");
  }
};

There is a long-hand way to define async components by returning an object instead of the result of the dynamic import. In that object, we can define a loading component:

const Tooltip = () => ({
  component: import("./Tooltip"),
  loading: AwesomeSpinner
});

In that way, after a default delay of 200ms, the component AwesomeSpinner will be shown. You can customize the delay:

const Tooltip = () => ({
  component: import("./Tooltip"),
  loading: AwesomeSpinner,
  delay: 500
});

The component you should use as your loading component must be as small as posible, so that it loads almost instantly.

Error Component

In the same way, we can define an error component in the long-hand form of the lazily loaded component:

const Tooltip = () => ({
  component: import("./Tooltip"),
  loading: AwesomeSpinner,
  error: SadFaceComponent
});

The SadFaceComponent will be shown when there is an error loading the "./Tooltip" component. That could happen in several cases:

  • The internet is down
  • That component doesn’t exist (this is a good way to try it, by intentionally deleting it yourself)
  • A timeout is met

By default, there is no timeout, but we can configure it ourselves:

const Tooltip = () => ({
  component: import("./Tooltip"),
  loading: AwesomeSpinner,
  error: SadFaceComponent,
  timeout: 5000
});

Now, if after 5000 milliseconds the component hasn’t load, the error component will be shown.

Wrapping Up

You’ve seen how a component is split in its own chunk file and how it’s lazily loaded using the dynamic import. We’ve also deferred the chunk loading by conditionally rendering it.
While async components can improve an app loading time by splitting and deferring the loading of their chunks, they could have an impact on UX especially when they are big. Having control over the loading state allows us to provide feedback and engage the user in the case when that slowness is noticeable.

Master Vue.js with Vue School

Top notch Vue.js courses and over 200 lessons for just $12 per month!


Article written by Alex Jover Morales

Passionate web developer. Author of Test Vue.js components with Jest on Leanpub. I co-organize Alicante Frontend. Interested in web performance, PWA, the human side of code and wellness. Cat lover, sports practitioner and good friend of his friends. His bike goes with him.