Lazy loading components is a easy way to improve the user experience of your app especially if your code bundle is big or if users are on slow connections.

Vue 3 has introduced several new features to help you achieve this easily and with great UX through the improvements to the async component API and the new Suspense component.

Table of contents:


    Why lazy load components?

    Some parts of your UI don't need to be loaded the instant a user visits your app, for example, dynamic UI features like modals and tooltips, etc. And, if you're using the single-page app architecture, page content on unseen pages shouldn't be loaded until needed either.

    You can get an easy performance win by "lazy loading" the components that contain such features and content. This means the code for these components is not included in the initial code bundle sent to a user and is instead loaded on demand.

    Example scenario

    In this example app, our app displays a component ChatWindow which loads if the user is authenticated.

    The details aren't important, but let's assume authentication can only be determined at runtime, and that this component is big and bulky. For these reasons, we may want to lazy load it.

    App.vue

    <template>
      <h3>Chat with friends here</h3>
      <chat-window v-if="auth" />
    </template>
    <script>
    import ChatWindow from "@/components/ChatWindow";
    
    export default {
      components: {
        ChatWindow
      },
      ...
    }
    </script>
    

    Lazy loading with Vue 3 async component API

    Vue 3 has introduced the defineAsyncComponent API which makes it very simple to lazy load a component.

    All you need to do is pass a function to the constructor that loads your component. Assuming you're bundling your code with Webpack or something similar, the easiest way to do this is to use the dynamic import feature (import) which will ensure your component is built into a separate file and loaded only when called upon.

    App.vue

    <script>
    import { defineAsyncComponent } from "vue";
    
    const ChatWindow = defineAsyncComponent(
      () => import("@/components/ChatWindow")
    );
    
    export default {
      components: {
        ChatWindow
      },
      ...
    }
    </script>
    

    When this app is built, you'll see any dynamically imported component as a separate file in your build.

    File                                 Size
    
    dist/js/chunk-vendors.f11402df.js    82.39 KiB
    dist/js/app.ada103fb.js              20.59 KiB
    dist/js/ChatWindow.3c1708e4.js       5.47 KiB
    dist/css/app.8221c481.css            1.76 KiB
    dist/css/ChatWindow.f16731cd.css     2.75 KiB
    

    For more info on how this works, see my previous article Code Splitting With Vue.js And Webpack.

    Loading-state content

    The downside of the lazy-loading approach is that the load time you saved by removing it from the initial bundle will need to be incurred when the component is used. This means, for a short period while the rest of the app is loaded, the lazy-loaded part of your UI may be missing.

    One pattern to deal with this is to show a "loading-state" component while the requested component is being retrieved.

    Here you can see what the app might look like in the first few moments when it loads if we used a spinner for loading state (on the left) and it's fully-loaded state (on the right).

    Async components

    The async component API allows you to include a loading component by passing an options object to the defineAsyncComponent constructor and specifying it there.

    App.vue

    <script>
    import { defineAsyncComponent } from "vue";
    import Spinner from "@/components/Spinner.vue";
    
    const ChatWindow = defineAsyncComponent({
      loader: () => import("@/components/ChatWindow"),
      loadingComponent: Spinner
    });
    
    export default {
      components: {
        ChatWindow
      },
      ...
    }
    </script>
    

    Are You Ready For Vue 3?

    Join our free four-part email course to learn the key changes in Vue 3 that you need to be aware of!

    This subscription also includes Vue.js Developers promotional emails. You can opt-out at any time. View our privacy policy .

    Flexible loading state with Suspense

    This approach to loading state works just fine but is a little restrictive. For example, you might like to pass props to the loading-state component, pass content to its slot, etc, which is not easily achievable using the async component API.

    To add more flexibility, we can use the new Suspense component, also added in Vue 3. This allows us to determine async loading content at a template level using slots.

    Suspense is a global component (like transition) and can be used anywhere in your Vue 3 app. To use it, declare it in your template, and include two named slots: default and fallback.

    Suspense will ensure the default slot is displayed when the async content loads, and the fallback slot is used as the loading state.

    <template>
      <Suspense>
        <template #default>
          <h3>Chat with friends here</h3>
          <chat-window />
        </template>
        <template #fallback>
          <spinner color="blue" />
        </template>
      </Suspense>
    </template>
    <script>
    import { defineAsyncComponent } from "vue";
    import Spinner from "@/components/Spinner.vue";
    
    const ChatWindow = defineAsyncComponent(
      () => import("@/components/ChatWindow")
    );
    
    export default {
      components: {
        ChatWindow,
        Spinner
      },
      ...
    }
    </script>
    

    If you want to learn more about lazy loading in Vue, check out this article by Filip Rakowski.


    Anthony Gore

    About Anthony Gore

    I'm Anthony Gore and I'm here to teach you Vue.js! Through my books, online courses, and social media, I aim to turn you into a Vue.js expert.

    I'm a Vue Community Partner, curator of the weekly Vue.js Developers Newsletter, and the creator of Vue.js Developers.

    If you enjoyed this article, show your support by buying me a coffee. You might also enjoy taking one of my online courses!