6

New to Vue and Vite but trying to get dynamic layouts working properly here. I believe I have what is needed but the issue it the meta seems to always come up as an empty object or undefined.

AppLayout.vue

<script setup lang="ts">
  import AppLayoutDefault from './stub/AppLayoutDefault.vue'
  import { markRaw, watch } from 'vue'
  import { useRoute } from 'vue-router'

  const layout = markRaw(AppLayoutDefault)
  const route = useRoute()

  console.log('Current path: ', route.path)
  console.log('Route meta:', route.meta)

  watch(
    () => route.meta,
    async (meta) => {
      try {
        const component = await import(`./stub/${meta.layout}.vue`)
        layout.value = component?.default || AppLayoutDefault
      } catch (e) {
        layout.value = AppLayoutDefault
      }
    },
    { immediate: true }
  )
</script>

<template>
  <component :is="layout"> <router-view /> </component>
</template>

App.vue

<script setup lang="ts">
  import AppLayout from '@/layouts/AppLayout.vue'
</script>
<template>
  <AppLayout>
    <router-view />
  </AppLayout>
</template>

Each and every route has the appropriate meta set with a property called layout.

I just can't seem to get he layout applied correctly on the first load or any click of a link in the navbar(which are just router-link) for that matter.

2 Answers 2

10

The main problem is layout is initialized as markRaw(), which is not a reactive ref:

const layout = markRaw(AppLayoutDefault) // ❌ not reactive

Solution

  1. Initialize layout as a ref.
  2. Instead of watching the full route object, watch route.meta?.layout because that's the only relevant field for the handler.
  3. Wrap layout's new values with markRaw() to avoid reactivity on the component definition.
const layout = ref() 1️⃣

watch(
  () => route.meta?.layout as string | undefined, 2️⃣
  async (metaLayout) => {
    try {
      const component = metaLayout && await import(/* @vite-ignore */ `./${metaLayout}.vue`)
      layout.value = markRaw(component?.default || AppLayoutDefault) 3️⃣
    } catch (e) {
      layout.value = markRaw(AppLayoutDefault) 3️⃣
    }
  },
  { immediate: true }
)

demo

Sign up to request clarification or add additional context in comments.

Comments

1

The solution from Tony is great. But you need to add computed inside the watch because it will prevent code execution for the very first "layout" variable initialized which is still undefined and it will cause unnecessary rendering. So, please add the computed function inside the watch function. Also you need to watch "route.path" not the "route.meta?.layout".

import DefaultLayout from '@/layouts/Default.vue'
import { markRaw, ref } from '@vue/reactivity'
import { computed, watch } from '@vue/runtime-core'
import { useRoute } from 'vue-router'

const layout = ref()
const route = useRoute()

watch(
  computed(() => route.path), async () => {
    let metaLayout = route.meta?.layout

    try {
      const metaLayoutComponent = metaLayout && await import(`./layouts/${metaLayout}.vue`)

      layout.value = markRaw(metaLayoutComponent?.default || DefaultLayout)
    } catch (error) {
      layout.value = markRaw(DefaultLayout)
    }
  }
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.