

vite-plugin-layouts
Router based layouts for Vue 3 applications using Vite.
Features
- 📁 File Based Layouts are stored in
/src/layouts
as standard Vue components
- 🔄 Sensible Defaults Pages without a layout use
default.vue
automatically
- 🌐 Multiple Layouts Support for multiple layout directories
- 🎨 Meta Configuration Specify layouts via route blocks in your pages
- 🔌 Router Integration Pairs with
unplugin-vue-router
- 📱 HMR Optimized Client-side layout mode for faster HMR
- 🛠️ Flexible Configuration Customize layout directories, exclusions, and more
Install
npm install -D vite-plugin-layouts
# or
yarn add -D vite-plugin-layouts
# or
pnpm add -D vite-plugin-layouts
# or
bun add -D vite-plugin-layouts
Get Started
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Layouts from 'vite-plugin-layouts'
import Pages from 'vite-plugin-pages'
export default defineConfig({
plugins: [
Vue(),
Pages(),
Layouts()
]
})
Then in your main.ts file:
// main.ts
import { setupLayouts } from 'virtual:generated-layouts'
import { createRouter } from 'vue-router'
import generatedRoutes from '~pages'
const routes = setupLayouts(generatedRoutes)
const router = createRouter({
// ...
routes,
})
If you're using unplugin-vue-router
:
// main.ts
import { setupLayouts } from 'virtual:generated-layouts'
import { createRouter } from 'vue-router/auto'
const router = createRouter({
// ...
extendRoutes: routes => setupLayouts(routes),
})
TypeScript Support
If you want type definition for the virtual modules, add the following to your tsconfig.json
:
{
"compilerOptions": {
"types": ["vite-plugin-layouts/client"]
}
}
Usage
Layouts are stored in the /src/layouts
folder by default and are standard Vue components with a <router-view></router-view>
in the template.
You can specify which layout to use for a page by using a route block:
<route lang="yaml">
meta:
layout: users
</route>
This will look for /src/layouts/users.vue
for the page's layout. If no layout is specified, it will use default.vue
.
Configuration
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Layouts from 'vite-plugin-layouts'
import Pages from 'vite-plugin-pages'
export default defineConfig({
plugins: [
Vue(),
Pages(),
Layouts({
layoutsDirs: 'src/layouts', // default: 'src/layouts'
pagesDirs: 'src/pages', // default: 'src/pages'
defaultLayout: 'default', // default: 'default'
exclude: [], // Patterns to exclude from layout loading
})
]
})
Available Options
Option | Type | Default | Description |
---|
layoutsDirs | string | string[] | 'src/layouts' | Path(s) to the layouts directory. Supports globs. |
pagesDirs | string | string[] | null | 'src/pages' | Path(s) to the pages directory. Set to null to watch all files. |
defaultLayout | string | 'default' | Name of the default layout to use when none is specified. |
exclude | string[] | [] | Patterns to exclude from layout loading. Files named __*__.vue are automatically excluded. |
Advanced Configuration: ClientSideLayout
For faster HMR and more efficient loading, you can use the ClientSideLayout mode:
// vite.config.ts
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import { ClientSideLayout } from 'vite-plugin-layouts'
import Pages from 'vite-plugin-pages'
export default defineConfig({
plugins: [
Vue(),
Pages(),
ClientSideLayout({
layoutsDir: 'src/layouts', // default: 'src/layouts'
defaultLayout: 'default', // default: 'default'
importMode: 'sync' // Auto-detect: 'sync' for SSG, 'async' for others
})
]
})
How It Works
The setupLayouts
function transforms your routes by:
- Replacing pages with their specified layouts
- Making the original pages children of their layouts
This creates nested routes with the same paths, giving you full access to the vue-router API.
Common Patterns
Transitions Between Routes
To add transitions between routes, including when using the same layout:
<!-- App.vue -->
<template>
<router-view v-slot="{ Component, route }">
<transition name="slide">
<component :is="Component" :key="route" />
</transition>
</router-view>
</template>
Passing Data from Layout to Page
Use props to pass data down from layout to page:
<router-view foo="bar" />
Setting Static Data for a Layout
Use the route's meta property in your page:
<!-- page.vue -->
<template>
<div>Content</div>
</template>
<route lang="yaml">
meta:
layout: default
bgColor: yellow
</route>
Then in your layout:
<!-- layout.vue -->
<script setup>
import { useRouter } from 'vue-router'
</script>
<template>
<div :style="`background: ${useRouter().currentRoute.value.meta.bgColor};`">
<router-view />
</div>
</template>
Dynamic Data from Page to Layout
Use custom events to pass data from page to layout:
<!-- page.vue -->
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['setColor'])
if (2 + 2 === 4)
emit('setColor', 'green')
else
emit('setColor', 'red')
</script>
Listen for the events in your layout:
<!-- layout.vue -->
<script setup>
import { ref } from 'vue'
const bgColor = ref('yellow')
function setBg(color) {
bgColor.value = color
}
</script>
<template>
<main :style="`background: ${bgColor};`">
<router-view @set-color="setBg" />
</main>
</template>
Testing
bun test
Changelog
Please see our releases page for more information on what has changed recently.
Contributing
Please review the Contributing Guide for details.
Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
Discussions on GitHub
For casual chit-chat with others using this package:
Join the Stacks Discord Server
Postcardware
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where vite-plugin-layouts
is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎
Sponsors
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
Credits
License
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙