4、页面与布局:pages页面创建、layouts布局系统、error页面定制、app.vue入口文件

好,咱们今天聊聊 Nuxt3 里最核心的几个概念——页面、布局、错误处理和入口文件。说实话,这几个东西你搞明白了,整个项目的骨架基本就搭起来了。我刚开始用 Nuxt 的时候,也在这上面踩过不少坑,今天一并分享给你。

4.1 pages 页面创建——约定大于配置

Nuxt3 的页面系统,说白了就是「文件即路由」。你在 pages/ 目录下放一个 about.vue,它就自动生成了 /about 路由。这个设计我个人非常喜欢,省去了手动配路由的麻烦。

核心规则:pages 目录下的每个 .vue 文件,自动映射为一个路由。

来看个实际例子:

pages/
├── index.vue          → /
├── about.vue          → /about
├── blog/
│   ├── index.vue      → /blog
│   └── [id].vue       → /blog/:id
└── user/
    └── [id]/
        └── profile.vue → /user/:id/profile

嗯,这里要注意动态路由的写法。用方括号 [param] 表示动态参数,在页面里通过 useRoute() 获取:

<!-- pages/blog/[id].vue -->
<script setup>
const route = useRoute()
console.log(route.params.id) // 比如 /blog/123 就输出 123
</script>

我在项目中遇到过一个问题:嵌套路由的页面刷新后 404。后来发现是文件名大小写搞错了。Nuxt3 对大小写敏感,[Id].vue[id].vue 是两个不同的路由。建议统一用小写。

4.2 layouts 布局系统——复用页面结构

布局系统解决的是「多个页面共享相同结构」的问题。比如每个页面都有导航栏和页脚,你总不能在每个页面都写一遍吧?

Nuxt3 的布局文件放在 layouts/ 目录下,默认布局是 default.vue

<!-- layouts/default.vue -->
<template>
  <div>
    <header>网站导航栏</header>
    <slot />  <!-- 页面内容会渲染到这里 -->
    <footer>版权信息</footer>
  </div>
</template>

页面里怎么用?很简单,页面组件不需要任何额外配置,默认就会套用 default.vue 布局。如果你想用其他布局,在页面里指定:

<!-- pages/login.vue -->
<script setup>
definePageMeta({
  layout: 'auth'  // 使用 layouts/auth.vue
})
</script>

小技巧:我习惯把布局按功能拆分。比如后台管理用 admin.vue,前台展示用 default.vue,登录注册用 auth.vue。这样每个布局职责清晰,维护起来也方便。

布局系统还有一个隐藏功能——嵌套布局。你可以在一个布局里引用另一个布局:

<!-- layouts/admin.vue -->
<template>
  <div class="admin-layout">
    <aside>侧边栏</aside>
    <main>
      <slot />
    </main>
  </div>
</template>

然后页面里用 definePageMeta({ layout: 'admin' }) 即可。注意,布局文件不要放在 layouts/ 的子目录里,Nuxt3 只识别一级目录下的布局文件。

4.3 error 页面定制——优雅处理异常

用户访问不存在的页面,或者服务端渲染报错了,总不能给用户看一个白屏吧?Nuxt3 允许你定制错误页面,放在项目根目录的 error.vue 里。

我曾经犯过一个错误:把 error.vue 放在了 pages 目录下。结果死活不生效。后来才明白,error.vue 是顶层文件,和 pages、layouts 平级。

<!-- error.vue(项目根目录) -->
<script setup>
const props = defineProps({
  error: Object
})

const handleError = () => clearError({ redirect: '/' })
</script>

<template>
  <div class="error-page">
    <h1>{{ error.statusCode }}</h1>
    <p>{{ error.statusMessage }}</p>
    <button @click="handleError">返回首页</button>
  </div>
</template>

error 对象包含三个关键属性:

属性 说明 示例值
statusCode HTTP 状态码 404、500
statusMessage 状态描述 "Not Found"
message 详细错误信息 "Page not found"

注意:error.vue 本身不能使用布局系统。因为它是在布局之外渲染的。如果你需要样式,直接在 error.vue 里写内联样式或引入全局 CSS 即可。

你还可以在页面里手动触发错误:

<script setup>
const { data } = await useFetch('/api/something')
if (!data.value) {
  throw createError({
    statusCode: 404,
    statusMessage: '数据不存在'
  })
}
</script>

嗯,这里有个细节:createError 在服务端会触发 500 错误页面,在客户端则跳转到错误页面。所以建议在 useFetchonError 回调里处理,而不是直接 throw。

4.4 app.vue 入口文件——应用的起点

app.vue 是 Nuxt3 应用的根组件。它和 Vue 3 的 App.vue 类似,但有一些 Nuxt 特有的功能。

默认的 app.vue 很简单:

<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

这里 <NuxtLayout> 负责加载布局,<NuxtPage> 负责渲染当前路由对应的页面。你可以在 app.vue 里做全局的事情:

  • 引入全局 CSS 或字体
  • 设置页面过渡动画
  • 添加全局的 loading 状态
  • 注入全局的插件或组件

举个例子,我习惯在 app.vue 里做页面切换的过渡动画:

<template>
  <NuxtLayout>
    <NuxtPage :transition="{
      name: 'page',
      mode: 'out-in'
    }" />
  </NuxtLayout>
</template>

<style>
.page-enter-active,
.page-leave-active {
  transition: opacity 0.3s;
}
.page-enter-from,
.page-leave-to {
  opacity: 0;
}
</style>

个人建议:app.vue 里不要写太多业务逻辑。它就像房子的地基,越干净越好。全局的请求拦截、状态管理,放到 plugins 或 middleware 里更合适。

还有一个容易忽略的点:如果你删除了 app.vue,Nuxt3 会自动使用默认的入口文件。但如果你创建了 app.vue,就必须包含 <NuxtLayout><NuxtPage>,否则页面渲染不出来。我曾经删掉 <NuxtLayout> 想偷懒,结果页面空白了半天才反应过来。

好了,这四个概念你掌握了吗?说白了就是:pages 管路由,layouts 管结构,error.vue 管异常,app.vue 管全局。把这四个文件的关系理清楚,Nuxt3 项目的骨架你就拿捏了。