Router based layout for Vue 3 applications using Vite
This works best along with the vite-plugin-pages.
Layouts are stored in the /src/layouts
folder by default and are standard Vue components with a <router-view></router-view>
in the template.
Pages without a layout specified use default.vue
for their layout.
You can use route blocks to allow each page to determine its layout. The block below in a page will look for /src/layouts/users.vue
for its layout.
See the Vitesse starter template for a working example.
<route lang="yaml">
meta:
layout: users
</route>
Install Layouts:
$ npm install -D vite-plugin-vue-layouts
Add to your vite.config.js
:
import Vue from '@vitejs/plugin-vue';
import Pages from 'vite-plugin-pages';
import Layouts from 'vite-plugin-vue-layouts';
export default {
plugins: [Vue(), Pages(), Layouts()],
};
In main.ts, you need to add a few lines to import the generated code and setup the layouts.
import { createRouter } from 'vue-router'
import { setupLayouts } from 'virtual:generated-layouts'
import generatedRoutes from 'virtual:generated-pages'
const routes = setupLayouts(generatedRoutes)
const router = createRouter({
// ...
routes,
});
If you want type definition of virtual:generated-layouts
, add vite-plugin-vue-layouts/client
to compilerOptions.types
of your tsconfig
:
{
"compilerOptions": {
"types": ["vite-plugin-vue-layouts/client"]
}
}
interface UserOptions {
layoutsDir?: string
exclude: string[]
}
To use custom configuration, pass your options to Pages when instantiating the plugin:
// vite.config.js
import Layouts from 'vite-plugin-vue-layouts';
export default {
plugins: [
Layouts({
layoutsDir: 'src/mylayouts',
}),
],
};
Relative path to the layouts directory. Supports globs. All .vue files in this folder are imported async into the generated code.
Any files named __*__.vue
will be excluded, and you can specify any additional exclusions with the exclude
option
Default: 'src/layouts'
setupLayouts
transforms the original router
by
- Replacing every page with its specified layout
- Appending the original page in the
children
property.
Simply put, layouts are nested routes with the same path.
Before:
router: [ page1, page2, page3 ]
After setupLayouts()
:
router: [
layoutA: page1,
layoutB: page2,
layoutA: page3,
]
That means you have the full flexibility of the vue-router API at your disposal.
Layouts and Transitions work as expected and explained in the vue-router docs only as long as Component
changes on each route. So if you want a transition between pages with the same layout and a different layout, you have to mutate :key
on <component>
(for a detailed example, see the vue docs about transitions between elements).
App.vue
<template>
<router-view v-slot="{ Component, route }">
<transition name="slide">
<component :is="Component" :key="route" />
</transition>
</router-view>
</template>
Now Vue will always trigger a transition if you change the route.
If you want to send data down from the layout to the page, use props
<router-view foo="bar" />
If you want to set state in your page and do something with it in your layout, add additional properties to a route's meta
property. Doing so only works if you know the state at build-time.
You can use the <route>
block if you work with vite-plugin-pages.
In page.vue
:
<template><div>Content</div></template>
<route lang="yaml">
meta:
layout: default
bgColor: yellow
</route>
Now you can read bgColor
in layout.vue
:
<script setup>
import { useRouter } from 'vue-router'
</script>
<template>
<div :style="`background: ${useRouter().currentRoute.value.meta.bgColor};`">
<router-view />
</div>
</template>
If you need to set bgColor
dynamically at run-time, you can use custom events.
Emit the event in page.vue
:
<script setup>
import { defineEmit } from 'vue'
const emit = defineEmit(['color'])
if (2 + 2 === 4)
emit('setColor', 'green')
else
emit('setColor', 'red')
</script>
Listen for setColor
custom-event in layout.vue
:
<script setup>
import { ref } from 'vue'
const bgColor = ref('yellow')
const setBg = (color) => {
bgColor.value = color
}
</script>
<template>
<main :style="`background: ${bgColor};`">
<router-view @set-color="setBg" />
</main>
</template>