Vue3: Nested Routes and Dynamic Layout component

716 Views Asked by At

Hi Vue enthusiasts out there, I have been working on an multi-tenant application and stuck at dynamic layout problem.

Requirement: Load tenant specific layout.vue file from public folder and wrap <router-view> around it.

Tried few things like dynamic imports, defineAsyncComponent etc but couldn't get it working.

   // router:
        import store from '../store/index';
        import NestedApp from '../views/NestedApp.vue';
        // const layoutA = () => defineAsyncComponent(import(store.getters.pageLayout('LayoutA')));

        const routes = [
          {
            path: '/:tenant:/:locale',
            name: 'NestedApp',
            component: NestedApp,
            children: [
             {
                path: 'about',
                name: 'About',
                component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
                meta: { layout: () => import(store.getters.pageLayout('LayoutA')) }
              }
            ]
        ]


    // NestedApp.vue:
    <template>
      <div class="NestedApp">
        <navbar/>
        <component :is="layoutWrapper">
          <router-view/>
        </component>
      </div>
    </template>
    <script>
    import Navbar from '../components/Navbar.vue';
    export default {
      name: 'NestedApp',
      components: {
        Navbar,
      },
      computed: {
        layoutWrapper() {
          console.info(`layout: ${this.$route.meta.layout}`);
          return this.$route.meta.layout || 'div';
        }
      }
    }
    
    // LayoutA.vue:
    <template>
      <div class="LayoutA">
          <span>Layout A</span>
          <slot/>
      </div>
  </template>
   

I get following error in browser console: enter image description here

1

There are 1 best solutions below

1
On BEST ANSWER

Got a workaround to this problem. Sending component via template string from backend API call and then creating a component out of it via defineComponent and markRaw methods.

API response:

"Layouts": {
        "LayoutA": {
            "name": "LayoutAbout",
            "template": "<div class='LayoutA' style='background-color: darkgray'><span>Layout A</span><slot/></div>"
        }
},

and then use in App.vue:

    import { defineComponent, markRaw } from 'vue';

    export default {
      name: 'App',
      methods: {
        loadLayout(pageLayout) {
          const layout = this.$store.getters.pageLayout(pageLayout);

          this.layoutWrapper = layout ? defineComponent(markRaw({...layout})) : 'div'; 
        }
      },
    created() {
      this.loadLayout(this.$route.meta.layout);
    },
    beforeRouteUpdate(to) {
      this.loadLayout(to.meta.layout);
    },
}
<template>
  <div class="App">
    <navbar/>
    <component :is="layoutWrapper">
      <router-view/>
    </component>
  </div>
</template>