DEV Community

GreggHume
GreggHume

Posted on • Edited on

Vue 3, Nuxt 3 and Vite: Dynamic import components using api data

You want to dynamically import components based on some dataset or api call and perhaps wait for those components to fully download before rendering the page, here is an example:

  • Dont forget if you are using async setup() in Vue 3 (not Nuxt) you will have to wrap you components in <Suspense> or you will see nothing render.

Options API

<template>
  <div>
    <component 
    v-once
    v-for="(component, index) in components" 
    :is="component.component"
    v-bind="component.data"
    :key="index"
    />
  </div>
</template>

<script>
  import { defineAsyncComponent } from 'vue';

  export default {
    data:()=> ({
      page: {},
      components: []
    }),
    async created() {
      // dataset
      const page_req = await fetch('/data/page-data.json')
      const {components, ...page) = await page_req.json()

      this.page = page

      // import components
      if (components) {
        const dynamic_components = components.map((component)=> {
          const {type, ...data} = component;
          // split the data and the component instance so you can v-bind the data easier in the template
          return {
            data: data,
            component: defineAsyncComponent(() => import(`../components/Module/${type}.vue`))
          }
        })

        // download all components over the network before rendering the page
        // otherwise the page jumps around as components load in
        await Promise.all(dynamic_components.map((component)=> {
          return component.component.__asyncLoader()
        }))

        // we do not want components to be reactive data, this can cause performance issues, so we freeze the object
        // vue understands frozen objects should not be reactive.
        this.components = Object.freeze(dynamic_components)
      }
    }
  }
</script>
Enter fullscreen mode Exit fullscreen mode

Composition API

<template>
  <div>
    <component 
    v-once
    v-for="(component, index) in components" 
    :is="component.component"
    v-bind="component.data"
    :key="index"
    />
  </div>
</template>

// composition api
<script setup>
import { defineAsyncComponent } from 'vue';

// dataset (local data, or you can fetch from an api)
const page = await import('~/static/data/some-page-of-components.json')
let components = []

// import components
components = page.components.map((component)=> {
  const {type, ...data} = component;
  // split the data and the component instance so you can v-bind the data/props easier in the template
  return {
    data: data,
    // A note is that if you use path aliases for dynamic imports like @ or ~ you might experience issues.
    component: defineAsyncComponent(() => import(`../components/Module/${type}.vue`))
  }
})
</script>
Enter fullscreen mode Exit fullscreen mode

A note is that if you use path aliases for dynamic imports like @ or ~ you might experience issues.

Top comments (9)

Collapse
 
henninghov profile image
henninghov • Edited

Thanks a lot for the very informative post and for sharing. I have tested your approach and I am getting the components to load. I do get some 404 console log errors when loading the components though when running the page in the browser - saying the component is not found. It seems to be using the wrong path? Also it does not work when I separate the component name and file extension in the import statement. I have to combine the component name and the file extension. A bit hard to debug as the components do load. And is it possible to ask you to include the json structure you use for mocking your API? I am using Nuxt 3, Vite and VueJS with composition API. Thanks again!

Collapse
 
greggcbs profile image
GreggHume

Hey! Sorry about this late reply, i didnt get notified about your comment. Its a whole month later now but if you need any help I am happy to chat to you on a screen share. The code that I have posted above is used in production on a global top 10 insurers website so I can promise its good.

let me know!

Collapse
 
henninghov profile image
henninghov • Edited

Thanks a lot for the reply! I was able to get get the components loaded but I had to prefix the path with /_nuxt/ when loading the components - which seems a bit like an anti-pattern. I did make a bit of a different approach though which I found quite nice. In the :is binding in the loop I am calling a function that loads the component with defineAsyncComponent and return with return h(component). This way I can handle all the components in a reactive array and use array functions for adding, sorting and deleting components. Thanks again for the input and for the effort in the write up.

Thread Thread
 
greggcbs profile image
GreggHume

A caveat with doing it that way is that you will not be able to wait for all the components to load before showing the page. They will load in as they please causing the page to jump around, but if this is not an issue for you then your solution is perfect. For me it was an issue.

Here is one part of the site I built using my method. Note the loader when changing pages and the waiting of components to load before showing the page:
stage.msreinsurance.com/

Collapse
 
kissu profile image
Konstantin BIFERT

Hi! 👋🏻
Do you know why such aliases would be an issue? Seems strange that this may cause some disturbance. 🤔

Collapse
 
greggcbs profile image
GreggHume • Edited

It was said that the use of aliases was fixed in newer versions of Vite... I am yet to see this working in action.

I am also amending the code above to have options api example and thats difference because we have to set the components to non reactive data.

I have updated the code above to be a bit more friendly if you want to grab the updated version.

Collapse
 
kissu profile image
Konstantin BIFERT

Oh, my bad I guess. Wasn't aware that it was an issue before.

Looks better! 💪🏻

Collapse
 
artur_dorfman_206c9c23cbf profile image
Artur Dorfman

Why do we need v-once with ?

Collapse
 
greggcbs profile image
GreggHume • Edited

Because reactivity is not needed at this level. Saves some overhead.