Skip to content

Getting Started ​

Overview ​

vite-plugin-vue-layouts-next gives Vue Router 5 file-based routes a layout system. Layouts live in src/layouts and are ordinary Vue components with a <router-view> in the template; every page is wrapped in one.

It consists of two parts:

  • A Vite plugin that scans your layouts directory and generates the virtual:generated-layouts module.
  • A setupLayouts helper that rewrites your route records so each page becomes a child of its layout.

Pages that do not choose a layout use default.vue. Pages that do choose one name it through meta.layout, using a normalized layout name.

You can learn more about the rationale behind the fork in the Why section, and about the route transformation in How it works.

Installation ​

bash
npm install -D vite-plugin-vue-layouts-next
bash
yarn add -D vite-plugin-vue-layouts-next
bash
pnpm add -D vite-plugin-vue-layouts-next

Compatibility Note ​

This package targets Vite 6 to 8, Vue 3.2+, and Vue Router 4.0.11 or 5. Page discovery is owned by Vue Router 5's own Vite plugin - this plugin only resolves layouts, so no page-routing plugin is needed alongside it.

Support Policy ​

Which versions of the peer dependencies a release of this plugin supports follows one rule set, so it does not have to be decided again for each release:

  • Vite - the current major and the two previous majors, mirroring Vite's own release policy. Today: Vite 6, 7 and 8.
  • Vue Router - the current major and the previous major; for each, only its latest minor is a support target. Today: Vue Router 5.x and 4.6.x. Older 4.x minors still install (the peer range starts at 4.0.11) but are not tested against.
  • Vue - no separate floor. Vue has no LTS or backport policy, so the supported Vue range is whatever the oldest supported Vue Router minor requires (Vue Router 4.6 requires Vue 3.5). The peer range currently still allows Vue 3.2+; features that need a newer Vue degrade gracefully there and say so in their docs (see Dynamic Layouts).
  • Narrowing any peer range is a breaking change and only happens in a major release, listed under "Removed" in the changelog. The next major is expected to move the floors to Vue Router 4.6 and Vue 3.5.

Adding the Plugin ​

Add it to your vite.config.ts, after VueRouter():

vite.config.js
js
import Vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import Layouts from 'vite-plugin-vue-layouts-next'
import VueRouter from 'vue-router/vite'

export default defineConfig({
  plugins: [VueRouter(), Vue(), Layouts()],
})

In main.ts, import Vue Router 5's generated file-based routes and wrap them with setupLayouts:

src/main.ts
js
import { setupLayouts } from 'virtual:generated-layouts'
import { createRouter } from 'vue-router'
import { routes } from 'vue-router/auto-routes'

const router = createRouter({
  // ...
  routes: setupLayouts(routes),
})

See the Config Section for the options Layouts() accepts.

Writing a Layout ​

A layout is a standard Vue component whose template renders a <router-view>:

src/layouts/default.vue
vue
<template>
  <div class="app">
    <header>My site</header>
    <router-view />
  </div>
</template>

Every page without an explicit layout renders inside this one.

Choosing a Layout Per Page ​

A page selects its layout through meta.layout, either in a <route> block:

src/pages/users.vue
vue
<route lang="yaml">
meta:
  layout: users
</route>

or with definePage:

src/pages/users.vue
vue
<script setup lang="ts">
definePage({
  meta: {
    layout: 'users',
  },
})
</script>

Both look for src/layouts/users.vue. Note that the value is a layout name, not a path - see Layout Names.

Passing Props to a Layout ​

meta.layout also accepts an object with the layout name and the props to pass to the layout component:

src/pages/dashboard.vue
vue
<script setup lang="ts">
definePage({
  meta: {
    layout: {
      name: 'panel',
      props: {
        sidebar: true,
        title: 'Dashboard',
      },
    },
  },
})
</script>

The layout receives them through defineProps:

src/layouts/panel.vue
vue
<script setup lang="ts">
defineProps<{
  sidebar?: boolean
  title?: string
}>()
</script>

<template>
  <div class="panel">
    <h1>{{ title }}</h1>
    <aside v-if="sidebar">
      ...
    </aside>
    <router-view />
  </div>
</template>
  • Omit name to pass props to defaultLayout; name: false renders the page without a layout, like layout: false.
  • Props a layout does not declare fall through as attributes on its root element.
  • A flat meta.layoutProps next to a string layout works too (layout.props wins when both are set).
  • definePage and <route> blocks are extracted statically, so props must be plain serializable values. To compute them at runtime, use a router guard or setPageLayout - see Dynamic Layouts.

Client Types ​

To get type definitions for virtual:generated-layouts, add the client types to your tsconfig:

tsconfig.json
json
{
  "compilerOptions": {
    "types": ["vite-plugin-vue-layouts-next/client"]
  }
}

Trying It Out ​

The repository ships four runnable setups covering SPA, SSG, client-side layouts and nested routes. See Examples.

Released under the MIT License.