布局系统 layouts
本教程共 50 篇 · 第 16 篇 · 更新于 2026-08-08 · 约 5 分钟阅读
本节目标:理解布局是什么,能创建默认布局与自定义布局,并用 NuxtLayout 包裹页面内容。
16-1
很多网站,几乎所有页面都共享同一套外壳:顶部导航栏、底部版权、两侧边栏。如果每个页面都把这些重复写一遍,又臭又长还难维护。布局(layouts)就是用来装这套「公共外壳」的。
你可以把布局理解成一个带「洞」的模板:洞里放的是会变化的页面内容,洞外是固定不变的公用界面。Nuxt 里这个「洞」用 <slot /> 表示。
16-2
在 Nuxt 4 中,布局文件放在 app/layouts/ 目录。其中 default.vue 是默认布局——只要你不特别指定,所有页面都会套用它。
<template>
<div>
<AppHeader />
<slot />
<AppFooter />
</div>
</template>
<slot /> 的位置,就是页面真正内容插入的地方。注意这里 <AppHeader />、<AppFooter /> 都是第 15 章讲过的自动导入组件,不用 import。
要在页面里启用布局,需要在 app/app.vue 里用 <NuxtLayout> 把 <NuxtPage /> 包起来:
<template>
<div>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</div>
</template>
这样,每个页面渲染出来时,都会被 default.vue 这个外壳包住。
Tip如果你的应用从头到尾只有一种布局,官方其实更推荐直接在
app.vue里写公共部分 +<NuxtPage />,不必引入 layouts 机制,更简单。布局适合「有多种外壳、页面间要切换」的场景。
16-3
除了默认布局,你可以建更多布局文件,比如 admin.vue、blank.vue。然后在具体页面里,用 definePageMeta 的 layout 字段告诉 Nuxt:这一页用哪个布局。
<template>
<div class="admin-shell">
<AdminSidebar />
<slot />
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin',
})
</script>
<template>
<h1>后台仪表盘</h1>
</template>
dashboard.vue 就会套上 admin.vue 这个外壳,而不是默认的 default.vue。layout 的值就是布局文件名(去掉 .vue)。
Warning自定义布局名要和文件名严格对应。如果写了
layout: 'Admin'但文件叫admin.vue,Nuxt 找不到对应布局会报错。名字用短横线还是大驼峰?Nuxt 内部会做归一化,但最稳妥的是和文件名保持一致写法。
16-4
布局也可以接收参数。在 definePageMeta 里用对象语法给 layout 传 name 和 props:
<script setup lang="ts">
definePageMeta({
layout: {
name: 'panel',
props: {
sidebar: true,
title: '仪表盘',
},
},
})
</script>
对应的布局用 defineProps 接收,而且 Nuxt 会做类型推导,编辑器里能补全:
<script setup lang="ts">
const props = defineProps<{
sidebar?: boolean
title?: string
}>()
</script>
<template>
<div>
<aside v-if="sidebar">侧边栏</aside>
<main>
<h1>{{ title }}</h1>
<slot />
</main>
</div>
</template>
16-5
某些页面(比如全屏登录页)可能不想套任何布局。把 layout 设为 false 即可:
<script setup lang="ts">
definePageMeta({
layout: false,
})
</script>
16-6
Nuxt 3 的布局目录在项目根 layouts/,Nuxt 4 改到 app/layouts/。<NuxtLayout> 的用法两版一致。另外,切换布局时若同时换了页面,Nuxt 会优先播放「布局过渡」而非「页面过渡」,这一点在第 21 章会说明。
16-7
要厘清一个概念:app.vue 是应用总入口,<NuxtLayout> 和 <NuxtPage> 都写在它里面;而布局文件是「被 <NuxtLayout> 包裹的那一层外壳」。层级是:app.vue → NuxtLayout(套用某个 layout)→ NuxtPage(渲染具体页面)。
如果你整个应用只有一种外壳,其实可以省略 layouts 目录,直接在 app.vue 里写公共部分 + <NuxtPage />。引入 layouts 的真正价值,是当不同板块需要「不同外壳」时,能灵活切换。
16-8
布局不一定要写死在某页。因为 layout 可以是一个 ref 或 computed,你能在运行时根据状态切换。例如用户进入「编辑模式」就切换到 edit 布局:
<script setup lang="ts">
const editing = ref(false)
definePageMeta({
layout: computed(() => editing.value ? 'edit' : 'view'),
})
</script>
Warning切换布局会触发布局过渡(下一章讲)。如果你同时改了页面内容,Nuxt 优先播「布局过渡」而不是「页面过渡」,这点和直觉略有不同,做动画时要心里有数。
16-9
把前面几章串起来,一个带布局的项目大致长这样:
-| app/
---| layouts/
-----| default.vue
-----| admin.vue
---| components/
-----| AppHeader.vue
-----| AppFooter.vue
---| pages/
-----| index.vue
-----| dashboard.vue
---| app.vue
default.vue 放页头页脚,dashboard.vue 通过 definePageMeta({ layout: 'admin' }) 换上后台外壳。层次清晰,复用也自然。
16-10
Nuxt 找布局是按名字在 app/layouts/ 下匹配文件名。若 definePageMeta 里写的 layout 找不到对应文件,会报错,所以命名要一一对应。另外,当某页没写 layout 字段时,自动套用 default.vue;若连 default.vue 都没有,Nuxt 就不加任何外壳直接渲染页面——这在某些极简场景(比如一个全屏落地页)是合理的行为,不用强行造一个空布局。
16-11
布局本质是「公共外壳」,判断标准其实就一条:这段 UI 是不是「跨多个页面都不变」。
适合放进布局的:顶部导航栏、底部版权、全局侧边栏、全站弹窗的挂载点(比如 toast 容器)、全局加载进度条。这些东西每个页面都要,写一处比在 N 个页面里各写一遍省心得多。
不适合放进布局的:只有某个页面才出现的业务内容、会随页面频繁变化的数据展示。这些应该留在页面组件或页面内部的子组件里。要是把「只有后台才用」的侧边栏塞进默认布局,前台页也会被它拖累,白白多渲染一块不相关的界面。
一句话记忆:布局装「所有页面共享、而且不怎么变」的东西。
Tip拿不准某段 UI 该不该进布局?先问自己:删掉这个页面后,它还在不在?如果还在,说明是公共的,进布局;如果跟着页面走,就留在页面里。
16-12
当你从用 default 布局的页面,跳到用 admin 布局的页面,Nuxt 会先卸载旧布局、再挂载新布局。这带来一个实际影响:布局自己维护的局部状态(比如一个折叠菜单是否展开、一个临时变量)在切换后会被重置。
如果你希望某种状态跨布局保留,得把它提到更上层——比如用 useState 存在应用级状态里,或者放进 app.vue,而不是指望布局实例帮你记住。
反过来,如果只是「同一布局内部」换页面(比如都在 default 下从 /about 跳 /contact),布局实例并不会重建,它里面的状态是保留的。理解了这点,你才知道什么该放布局、什么状态不能指望布局替你记着。
16-13
别把布局想得太神秘——app/layouts/default.vue 就是一个 Vue 单文件组件,组件能做的事它都能做:用 <script setup> 写逻辑、用 useRoute 读路由、用组合式函数拉数据。
比如你想在页头显示当前登录用户名,完全可以在 default.vue 里读取全局状态,再渲染进 <AppHeader />:
<script setup lang="ts">
const user = useUser() // 假设这是一个返回当前用户信息的组合式函数
</script>
<template>
<div>
<AppHeader :username="user.name" />
<slot />
</div>
</template>
布局只是「被 <NuxtLayout> 自动套到每个页面外面」而已,它内部的写法和普通页面没两样。
Note布局里如果要根据路由动态换内容(比如某些页隐藏页头),用
useRoute()判断即可,不必为此硬拆出一个新布局文件。只有当「外壳结构本身不同」时,才值得新建布局。
16-14
把前面的点串一下,Nuxt 选布局的顺序是:
- 页面
definePageMeta里写了layout吗?写了就用指定的(找不到对应文件就报错)。 - 没写
layout字段?套用default.vue。 - 连
default.vue都没有?不加任何外壳,直接渲染页面内容。
所以「切换布局」对你来说就是一句话的事:在目标页面写 definePageMeta({ layout: 'xxx' })。想回到默认外壳,把 layout 去掉或写成 'default' 就行。layout: false 则是彻底不要外壳(适合全屏登录页之类的场景)。
16-15
布局是套在页面外面的公共外壳,用 <slot /> 留出内容位置。默认布局是 default.vue,自定义布局靠 definePageMeta({ layout }) 指定,还能用对象语法传 props。布局里放「公共且稳定」的 UI,切换布局会重建布局实例、丢局部状态。下两章我们深入页面本身更进阶的形态:动态路由与嵌套路由。