规则手册的最后一页讲门面:应用入口怎么写、页面外围的导航页脚放哪、出错时用户看到什么。本节收束第 2 章,同时为第 3 章铺路——app.vue 里 NuxtPage 渲染出的页面组件,正是下一章 SSR 时序图里被服务器执行的对象。
有 layouts 与 pages 的完整应用,组件树是三层嵌套:
app.vue 应用最外层:全局只此一份 └─ NuxtLayout 布局层:导航、页脚、侧栏的容身之处 └─ NuxtPage 页面层:路由匹配到的页面组件
最简的 app.vue 只需两行:
<template> <NuxtLayout> <NuxtPage /> </NuxtLayout> </template>
两个内置组件的分工:NuxtLayout 负责找到当前页面要用的布局并渲染,NuxtPage 负责渲染路由匹配的页面。app.vue 适合放真正全局且恒定的东西(比如全局的加载进度条、跨布局的背景容器);一切"页面外围但可能因页面而异"的结构,都该下沉到布局。
布局放 layouts 目录,文件名即布局名。default.vue 是默认布局,页面不声明时自动套用:
<!-- layouts/default.vue --> <template> <div class="app-shell"> <nav class="topbar"> <NuxtLink to="/">首页</NuxtLink> <NuxtLink to="/products">商品</NuxtLink> <NuxtLink to="/about">关于</NuxtLink> </nav> <main class="content"> <slot /> <!-- 页面内容出现在这里 --> </main> <footer class="footer">接力商城 · 服务端渲染交接班示例站</footer> </div> </template>
注意布局不是页面组件的包装器,而是含 slot 的容器——页面通过插槽注入,这决定了布局切换时页面组件的销毁与重建行为(布局变了,slot 内容会重新挂载,页面状态不保留)。
需要第二种布局时,加一个命名布局文件,页面里声明选用:
<!-- layouts/bare.vue:无导航的极简布局,用于结账流程 --> <template> <div class="bare"> <slot /> </div> </template>
<!-- pages/checkout.vue:结账页用极简布局,聚焦付款 --> <script setup> definePageMeta({ layout: 'bare' }) </script> <template> <div>结账流程:不被顶部导航打扰</div> </template>
临时切换还可在页面里用 setLayout('bare') 动态换布局;definePageMeta({ layout: false }) 则完全脱离布局,页面全权自理。
项目根目录的 error.vue 是兜底错误页,渲染未捕获的错误(404、服务端异常、页面抛错):
<!-- error.vue --> <template> <div class="error-page"> <h1>{{ error.statusCode }}</h1> <p>{{ error.statusCode === 404 ? '这一棒没有接到:页面不存在' : '服务器掉棒了,稍后再试' }}</p> <button @click="handleError">回到首页</button> </div> </template> <script setup> const error = useError() function handleError() { clearError({ redirect: '/' }) // 清除错误状态并跳转 } </script>
要点有三:
clearError 必须调用,否则错误状态残留;参数里给 redirect 直接带跳转。它与页面内错误处理的分工:error.vue 管"没接住的",createError 与 showError 管"主动抛出的"。页面里遇到无效参数:
<script setup> const route = useRoute() if (!route.params.id) { // 主动制造带状态码的错误,触发错误页(fatal 表示整页接管) throw createError({ statusCode: 400, statusMessage: '缺少商品编号', fatal: true }) } </script>
第 4 章讲数据获取时会再遇到 createError——接口失败时抛出非致命错误,页面内局部展示,不打断整站。
综合本节与 2.1 的约定,动手搭一个三页面小站:
# 目录结构 # app.vue 两行骨架 # layouts/default.vue 导航加页脚 # pages/index.vue 首页 # pages/checkout.vue 结账页(命名布局 bare) # error.vue 兜底错误页
<!-- pages/index.vue --> <script setup> definePageMeta({ layout: 'default' }) // 默认布局可省略,此处显式示意 </script> <template> <section> <h1>首页</h1> <p>这段文字在服务端渲染后随 HTML 到达浏览器。</p> </section> </template>
访问一个不存在的路径(比如随手输入的乱码地址),error.vue 接管显示 404——整条门面链路就通了:app.vue 定壳、布局定框、页面定内容、错误页定兜底。
⚠️ 常见坑:把全局 CSS 的引入写在 default.vue 里。布局是可切换的,切到 bare 布局样式就丢了。全局样式在 nuxt.config 的 css 数组统一引入,布局里只放结构。
💡 关键直觉:app.vue、布局、页面是三级容器——恒定的进最外层,多数页面共享的进布局,只有自己用的进页面。拿不稳时问"换布局时它该不该跟着变"。