Scenario:
I am using Nuxt in universal
mode. The app works with a headless CMS that provides a simple layout of components that should be rendered, along with component names and their props - something like this:
[
{
"tag": "div",
"class": "col",
"component": "dogs",
"props": {
"dogs": [
{
"id": 1,
"name": "Barky"
},
{
"id": 2,
"name": "Jumpy"
}
]
}
},
{
"tag": "div",
"class": "col",
"component": "cats",
"props": {
"cats": [
{
"id": 1,
"name": "Miouwy"
},
{
"id": 2,
"name": "Fluffy"
}
]
}
}
]
As I understand, I have to apply the components to the DOM before Nuxt makes the "snapshot" and delivers it to the client. My plan was to mount the components in the created()
lifecycle - which is, only looking by the names, not the way to go.
The main problem:
I want to mount components dynamically to the DOM, which does not exist yet.
As an example of the thing I want to escape from - mounting the components after Nuxt delivered the snapshot - in the mounted()
lifecycle.
<template>
<section class="container">
<div ref="layout-container" class="row"></div>
</section>
</template>
<script>
import { mapGetters } from 'vuex';
import Vue from 'vue';
import Dogs from '@/components/Dogs';
import Cats from '@/components/Cats';
export default {
components: {
Dogs,
Cats
},
fetch({ store }) {
store.dispatch('landing/updateContent');
},
computed: {
...mapGetters({
content: 'landing/content',
})
},
beforeCreate() {
Vue.component('dogs', Dogs);
Vue.component('cats', Cats);
},
mounted() {
this.content.forEach((item) => {
const CompClass = Vue.component(item.component);
const instance = new CompClass({ propsData: item.props }).$mount();
this.$refs['layout-container'].appendChild(instance.$el);
})
}
};
</script>
Big thanks for any directions in advance!
EDIT: Repo with this example: https://github.com/jpedryc/nuxt-test-render
SOLUTION
My main problem was creating a rendered layout and trying to hook it up after the DOM was already delivered to the client. Instead, I should render the component within the virtual Vue DOM - right before the "snapshot" moment.
And that's what I did eventually - not mount a rendered component:
<template>
<section class="container">
<page-component/> <!-- A component without <template>, but with render() -->
</section>
</template>
The PageComponent consists only of:
import ...
export default {
components: {
Dogs,
Cats,
},
beforeCreate() {
Vue.component('dogs', Dogs);
Vue.component('cats', Cats);
},
render: function (h) {
return createDynamicLayout(h);
},
}
The createDynamicLayout(h)
is just a simple function that creates a tree of:
return h('div', { 'class': 'row' }, [
h('div', { 'class': 'col' }, h(<somwhere_call_dogs_component>)),
h('div', { 'class': 'col' }, h(<somwhere_call_cats_component>)),
])