首页 / Nuxt 4 入门教程 / 嵌套路由

Nuxt 4 入门教程

嵌套路由

本教程共 50 篇 · 第 18 篇 · 更新于 2026-08-08 · 约 5 分钟阅读

NuxtNuxt4嵌套路由NuxtPage命名视图

本节目标:理解嵌套路由的结构,能让父页面内部渲染子页面,并了解 v4.5 新增的命名视图。

18-1

前面讲的页面都是「整页替换」。但有些界面,外层框架不动、只有中间一块在变:比如后台系统,左侧菜单固定,右侧内容区在不同子页间切换。这种「页面里再嵌页面」的结构,就是嵌套路由(nested routes)。

Nuxt 用一种约定式的目录结构来表达它:父页面文件同名目录并存。目录里的文件,就是它的子路由。

-| app/pages/
---| parent/
-----| child.vue
---| parent.vue

这里 parent.vue 是父页面,parent/ 目录下的 child.vue 是子页面。Nuxt 会生成一条带 children 的路由:/parent 显示 parent.vue/parent/child 显示 parent.vue 套着 child.vue

18-2

关键点来了:子页面要显示在父页面的哪个位置,由父页面里的 <NuxtPage /> 决定。也就是说,父页面自己也得放一个出口。

<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage />
  </div>
</template>
<template>
  <p>我是子视图的内容</p>
</template>

访问 /parent/child 时,页面由上方的「我是父视图」和下方 child.vue 的内容共同组成。父页相当于一个「大布局」,子页填进它留出的洞里。

Note

嵌套路由里的 <NuxtPage /> 可以像普通组件一样传 props。比如 <NuxtPage :foobar="123" />,子页面用 defineProps 就能收到 foobar。这让父子页面之间能直接传值,不必走路由参数。

18-3

默认情况下,子路由切换时 <NuxtPage> 会跟着重新渲染。但有时你想更精细地控制「什么时候才算换了页」。可以用 pageKey 属性,或是在子页面里用 definePageMetakey

<template>
  <div>
    <h1>我是父视图</h1>
    <NuxtPage :page-key="route => route.fullPath" />
  </div>
</template>
<script setup lang="ts">
definePageMeta({
  key: route => route.fullPath,
})
</script>

key 的作用很像 Vue 里给组件加 :key:只要 key 变了,Nuxt 就认为是个新页面、强制重渲染;key 没变就尽量复用。这在做过渡动画、或想避免无谓重载时很有用。

18-4

从 Nuxt 4.5 起,一个路由可以往父页面里「多个出口」同时渲染。办法是用 name@view.vue 这种文件名约定:默认路由文件负责主内容,带 @名字 的兄弟文件负责其它出口。

-| app/pages/
---| parent/
-----| child.vue
-----| child@sidebar.vue
---| parent.vue

父页面里给每个出口起个名字:

<template>
  <div>
    <NuxtPage />
    <aside>
      <NuxtPage name="sidebar" />
    </aside>
  </div>
</template>

访问 /parent/child 时,child.vue 渲染进默认 <NuxtPage />child@sidebar.vue 渲染进 <NuxtPage name="sidebar" />。没有被匹配的命名出口就留空。

Warning

命名视图的元数据只在「默认路由文件」里读取。你在 child@sidebar.vue 里写的 definePageMeta 不会影响整条路由——要配置 meta,请写在 child.vue 里。

18-5

嵌套路由的基础机制(父文件 + 同名目录 + <NuxtPage>)在 Nuxt 3 与 Nuxt 4 一致,仅目录从 pages/ 变为 app/pages/命名视图是 Nuxt 4.5 才引入的新特性,Nuxt 3 及更早的 4.x 版本不支持,使用时请确认你的版本 ≥ 4.5.2(本教程基线版本已满足)。

18-6

容易混淆的是:布局和嵌套路由都能做出「外框不变、里面切换」的效果,区别在哪?布局是「跨页面通用外壳」,通常整站或整块共用;嵌套路由是「路由层级本身就有父子关系」,/parent/parent/child 在 URL 上就是上下级。简单说:布局管「外壳长相」,嵌套路由管「网址层级」。

18-7

设想一个后台:地址 /panel,左侧菜单常驻,右侧在「概览 / 订单 / 设置」间切换。结构可以这样:

-| app/pages/
---| panel/
-----| overview.vue
-----| orders.vue
-----| settings.vue
---| panel.vue

panel.vue 里放菜单 + <NuxtPage />,访问 /panel/orders 时右侧就显示 orders.vue。父子共用同一个外壳,却走的是真正不同的子路由——这正是嵌套路由的主场。

18-8

子路由切换时,想在菜单上高亮「当前子页」?结合第 20 章的 <NuxtLink> active 类,再配合 route 判断即可。因为子路由各自有独立路径,to="/panel/orders" 的链接在访问 /panel/orders 时会自动带上 router-link-active 类,高亮自然就来了。

18-9

嵌套路由里父子页面各有自己的 definePageMeta,meta 会合并(见第 14 章)。过渡方面,子路由切换时触发的是「页面过渡」,作用于 <NuxtPage /> 渲染的那块区域。如果你希望子路由切换也有独特的进出动画,可以在父页或子页用 pageTransition 配置,和顶层页面过渡是同一套机制,写法完全一致。

18-10

新手最容易犯的错:建好了 parent.vueparent/child.vue,访问 /parent/child 却只看到父页、看不到子页。九成原因是父页面里忘了写 <NuxtPage />。记住——父页不写出口,子页就无处安放。

这个 <NuxtPage /> 和外层 app.vue 里的那个不是同一个概念:app.vue 里的出口渲染「顶层页面」(比如 parent.vue 本身),parent.vue 里的出口渲染「它的子页面」(比如 child.vue)。两者是嵌套存在的,缺了哪一层,那一层的内容就不显示。

Warning

如果父页里压根没写 <NuxtPage />,Nuxt 不会报错,只是子路由永远渲染不出来。排查「子页空白」时,第一反应就去父页看有没有这个出口。

18-11

嵌套路由和布局叠加时,层级容易让人绕晕。完整的渲染链条是:

app.vue<NuxtLayout>(套某个 layout)→ <NuxtPage>(渲染顶层页面,如 parent.vue)→ parent.vue 里的 <NuxtPage>(渲染子页 child.vue

这里有个真坑:如果你在 parent.vue 里又写了一个 <NuxtLayout>,就形成了「布局套布局」,过渡动画和插槽层级都会变复杂,调试很痛苦。一般经验是——布局在 app.vue 那一层用一次就够,嵌套路由内部不要再引入第二层 <NuxtLayout>,除非你确实要「父页有自己独立的外壳」。

Warning

父页里的 <NuxtPage /> 如果同时配了过渡动画,会和顶层布局过渡互相影响。子页切换时,优先播的是作用在「父页内那个 <NuxtPage> 区域」的 pageTransition。别在两层同时设名字冲突的过渡,否则动画表现会难以预测。

18-12

除了前面讲过的「父页给 <NuxtPage> 传 props」,更松耦合的传值方式是 Vue 原生的 provide / inject。父页 provide 一个值,任意层级的子页都能 inject 拿到,不用一层层往下透传。

它适合「父页定义了菜单选中项,子页要读它」这类场景:

<script setup lang="ts">
// 父页提供当前选中的板块
provide('activeSection', ref('overview'))
</script>
<script setup lang="ts">
// 子页直接读取,不必走路由参数
const section = inject('activeSection')
</script>

注意 provide 的值如果是响应式的(ref / computed),子页拿到后依然会随父页变化而更新。这比 props 更适合「跨多层、又不想层层传递」的数据。

18-13

嵌套路由最适合「外壳固定、内页切换」的界面:后台面板、分步表单向导、带 Tab 切换的详情页。它的价值是让 URL 也反映出层级(/panel/orders 一眼看出在订单子页),同时共享父页状态。

不适合的情况是「两个页面结构完全不同、只是碰巧网址相邻」。硬塞进父子关系,反而要写一堆 v-if 判断谁是子页,失去简洁。这种情况还不如做成两个平级的顶层页面,各自独立。

Tip

判断要不要嵌套:问自己「这两个页面分享同一块不变的外壳吗?」分享就用嵌套,不分享就用平级路由。

18-14

嵌套路由靠「parent.vue + parent/ 目录」实现,父页里再放一个 <NuxtPage /> 作为子内容出口(漏写就空白);key 控制重渲染时机;Nuxt 4.5 的命名视图支持一个路由往多个带名出口渲染;父子传值可用 props 或 provide / inject。下一章我们看进入页面前能插手的「中间件」。