1

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
2
  • Have you tried to update your html template page to have the same background as your target page, in combination with beforeEach? Commented Oct 24, 2022 at 12:16
  • @StevenSpungin well it's not just about the background, in my app the default layout renders the content in a container surrounded by the navigation bar. I have simplified the problem in the example, but in my app I can see the navigation bar flicker and then the login form appear, which looks pretty bad Commented Oct 24, 2022 at 12:20

1 Answer 1

2

I have actually found an answer! It was caused by most examples on the internet not ever mentioning router.isReady()! I was curious why route.fullPath returned / on every page load, even when entering /login. This led me to this snippet:

router.isReady().then(() => app.mount())

And guess what, delaying app.mount() like that actually fixed my whole problem. Now the first layout that is rendered is actually the one configured in the route, even if it's not the default one.

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

1 Comment

where do you add the isReady to? can you update stackbiz demo on the top?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.