I'm using Vue 3 with vue-router and vite-plugin-pages, which by default loads all route components asynchronously.
I have made a simple layout component which switches between LayoutDefault and LayoutBlank, based on the route's meta. Blank is only for public sites like the login page, where user is not yet logged in. I made it this way so I only need to set the layout on few routes instead of all of them.
Because of the dynamic import though, when entering the site and being redirected to the login site via router's beforeEach guard, you can see the default layout being drawn for a split of a second and then switches to blank layout. This happens, because route.meta.layout in the watcher is always undefined at the beginning.
I could make /login to load synchronously, but I don't like this approach as I might be adding more "public" routes that should render in LayoutBlank, and same glitch also happens on them even when entering directly and not being redirected by the router.
Is there any fix to this other than switching the order of layouts so blank is default or loading everything synchronously? I tried to use another beforeEach hook instead of watching route.meta.layout but all it did, was moving the glitch to route leaving instead of entering when layouts were switched.
I couldn't use Vue3 in snippet so I put a simple demo code here: https://stackblitz.com/edit/vue3-vue-router-meta-layout-spc5tq?file=src%2Flayouts%2FAppLayout.vue,src%2Frouter.ts
When you reload the web container, you will notice the red "Default Layout" text flicker.
Some code:
<script setup lang="ts">
import AppLayoutDefault from './AppLayoutDefault.vue'
import AppLayoutBlank from './AppLayoutBlank.vue'
import { markRaw, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
const layout = ref()
const route = useRoute()
watch(
() => route.meta?.layout as string | undefined,
(metaLayout) => {
layout.value = markRaw(metaLayout === 'blank' ? AppLayoutBlank : AppLayoutDefault)
},
{ immediate: true }
)
</script>
<template>
<component :is="layout"> <router-view /> </component>
</template>
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Home,
},
{
path: '/login',
component: () => import('@/views/Login.vue'),
meta: { layout: 'blank', public: true },
},
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
meta: { layout: 'AppLayoutAdmin' },
},
],
})
let isLoggedIn = false
router.beforeEach((to) => {
if (!to.meta.public && !isLoggedIn) {
isLoggedIn = true
return 'login'
}
})
export default router