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>
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>
A note is that if you use path aliases for dynamic imports like @
or ~
you might experience issues.
Top comments (9)
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!
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!
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.
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/
Hi! ππ»
Do you know why such aliases would be an issue? Seems strange that this may cause some disturbance. π€
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.
Oh, my bad I guess. Wasn't aware that it was an issue before.
Looks better! πͺπ»
Why do we need v-once with ?
Because reactivity is not needed at this level. Saves some overhead.