首页
/ Nuxt 页面与布局迁移指南:从 Nuxt 2 的 `<Nuxt>`/`_id` 升级到 Nuxt 3/4 的 `<NuxtPage>` 与 `definePageMeta`

Nuxt 页面与布局迁移指南:从 Nuxt 2 的 `<Nuxt>`/`_id` 升级到 Nuxt 3/4 的 `<NuxtPage>` 与 `definePageMeta`

2026-09-07 18:05:35作者:仰钰奇

本指南围绕仓库中的 pages-and-layouts 迁移文档 展开,系统讲解将 Nuxt 2 应用的页面(Pages)与布局(Layouts)迁移到 Nuxt 3/4 时代所需的关键改动:布局渲染机制从 <Nuxt /> 切换到 <slot />,动态路由从 _id/_.vue 切换到 [id]/[...slug].vue,以及页面元信息从组件选项(layout/transition/key/keepalive)集中收口到 definePageMeta 编译宏。读完本文,你将能够对照逐条 diff 完成存量 Nuxt 2 项目页面与布局层的迁移,并理解 Nuxt 在 nuxt-layout.tsrouter.ts 中对应的底层实现。

迁移背景补充:在 Nuxt 3 中,整个框架基于 Vue 3 重写,官方推荐默认使用 Composition API 与 <script setup>(参见 迁移总览)。页面与布局迁移只是其中的一部分,其背后的共同主线是:凡是在 Nuxt 2 里通过组件选项(Options API)写在页面上的路由级行为,在 Nuxt 3/4 中统一改用 definePageMeta 编译宏表达


app.vue:新的应用级中央入口

Nuxt 3 不再要求你维护 nuxt.config.jslayouts/default.vue 并存的旧式根结构,而是提供一个集中式入口组件 ~/app.vue(对应目录文档 3.app.md)。

  • 如果你的源码目录中没有 app.vue 文件,Nuxt 会使用自己内置的默认版本,因此在迁移初期不创建它应用依然能运行。
  • app.vue 适合放置“整个应用启动时只需运行一次”的自定义逻辑,以及「每个页面都会出现」的公共组件。
  • 特别的,如果你只有一个布局,可以完全放弃 layouts 机制,把布局骨架直接写进 app.vue

迁移建议

  1. 创建 app.vue,并把需要在应用顶层执行一次的逻辑放进其中。
  2. 若项目仅存在单一布局,将其模板迁移进 app.vue,外层再包一层 <NuxtPage />
<template>
  <div>
    <!-- 所有页面共享的标记,例如导航栏 NavBar -->
    <NuxtPage />
  </div>
</template>

提示:app/pages/ 目录是可选的。如果应用只有单页,把它直接放进 app.vue 可以获得更轻量的构建产物——vue-router 不会被包含进 bundle。从源码结构看,这也是 Nuxt 官方在 directory-structure 文档 中明确推荐的单页瘦身方案。


Layouts 布局:用 <slot /> 取代 <Nuxt />

在 Nuxt 2 中,布局组件内部通过 <Nuxt> 组件来渲染当前页面;Nuxt 3/4 改为基于 Vue 3 的插槽机制,布局文件内的页面出口是 <slot />。这一改动同时解锁了命名插槽与作用域插槽等进阶用法(参见 1.layouts.md)。

关键改动清单

  1. <Nuxt /><slot />:把布局模板中的 <Nuxt /> 替换为 <slot />
  2. 布局选择方式变化:改用 definePageMeta 编译宏来声明页面使用的布局。
  3. 布局名称 kebab-case 化app/layouts/customLayout.vue 在页面中引用时写作 custom-layout。目录文档 1.layouts.md 中的命名对照表进一步印证了这套规则:~/layouts/desktop/default.vuedesktop-default~/layouts/desktop-base/base.vuedesktop-base~/layouts/desktop/index.vuedesktop;为清晰起见,官方建议让布局文件名与其名称一致(DesktopDefault.vuedesktop-default)。

布局迁移 diff

替换布局内的页面出口:

  <template>
    <div id="app-layout">
      <main>
-       <Nuxt />
+       <slot />
      </main>
    </div>
  </template>

使用 definePageMeta 声明页面所属布局:

+ <script setup>
+ definePageMeta({
+   layout: 'custom'
+ })
- <script>
- export default {
-   layout: 'custom'
- }
  </script>

该迁移同样适用于 component-options 迁移指南 中提到的 layout 选项迁移。


错误页迁移:_error.vueerror.vue + <NuxtLayout>

Nuxt 2 的错误页约定是 ~/layouts/_error.vue;Nuxt 3/4 将错误页提升为应用级组件 ~/error.vue,对应目录文档 3.error.md错误处理指南

<template>
  <div>
    <NuxtLayout name="default">
      <!-- -->
    </NuxtLayout>
  </div>
</template>

如上所示,如果希望错误页也复用布局,可以在 error.vue 内部显式使用 <NuxtLayout> 组件。<NuxtLayout> 由仓库源码 nuxt-layout.ts 提供,负责根据名称解析并渲染目标布局、并把页面的插槽内容分发进去。


Pages 页面:目录驱动的可选路由系统

Nuxt 3 的页面路由由 vue-router 提供,是否启用取决于源码目录中是否存在 app/pages/ 目录(参见 1.pages.md)。一旦存在该目录,其中每个文件都会自动生成一条路由。

  • 单页应用:如果只有一个页面,建议直接放入 app.vue,构建产物更小。
  • 多页应用:保持 app/pages/ 结构,但注意下面的文件命名变化。

从源码结构看,Nuxt 会扫描 app/pages/ 下的文件并生成路由记录,页面元信息(例如 keytransitionkeepalive)会在构建期从 definePageMeta 调用中静态抽取(对应 pages/utils.ts 中的 PAGE_META_MACRO_NAMES 与运行时宏实现 pages/runtime/composables.ts)。

动态路由(Dynamic Routes)命名变化

动态路由的文件名约定与 Nuxt 2 完全不同,需要重命名 app/pages/ 下的部分文件:

  1. Nuxt 2 用 _id 表达动态参数 → Nuxt 3/4 使用 [id]
  2. Nuxt 2 用 _.vue 表达 catch-all 路由 → Nuxt 3/4 使用 [...slug].vue

Nuxt 2 与 Nuxt 3 的文件→URL→参数对照:

- URL: /users
- Page: /pages/users/index.vue

- URL: /users/some-user-name
- Page: /pages/users/_user.vue
- Usage: params.user

- URL: /users/some-user-name/edit
- Page: /pages/users/_user/edit.vue
- Usage: params.user

- URL: /users/anything-else
- Page: /pages/users/_.vue
- Usage: params.pathMatch
- URL: /users
- Page: /pages/users/index.vue

- URL: /users/some-user-name
- Page: /pages/users/[user].vue
- Usage: params.user

- URL: /users/some-user-name/edit
- Page: /pages/users/[user]/edit.vue
- Usage: params.user

- URL: /users/anything-else
- Page: /pages/users/[...slug].vue
- Usage: params.slug

注意两处细节差异:

  • 动态参数读取名由 params.pathMatch 变为与文件名一致的 params.slug(catch-all 场景);
  • 目录文档 1.pages.md 还说明,catch-all 的参数是数组,例如导航到 /hello/world 会渲染 ["hello", "world"];若需要可选参数,则用双重方括号 [[slug]].vue

嵌套路由:<Nuxt>/<NuxtChild><NuxtPage>

Nuxt 2 中,父/子页面结构需要两个组件——父布局模板里的 <Nuxt> 与嵌套子路由处的 <NuxtChild>。Nuxt 3/4 统一用单个 <NuxtPage> 组件(对应组件文档 2.nuxt-page.md)完成页面出口渲染。

一个典型嵌套结构:

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

父页面 app/pages/parent.vue 中通过 <NuxtPage /> 渲染子页面 child.vue,并可向其传递 props(如 :foobar="123",子页面用 defineProps 接收)。

页面 key 与 keep-alive 属性迁移

Nuxt 2 中你可以给 <Nuxt> 传自定义的页面 key 或 keep-alive 属性;Nuxt 3/4 中这些选项统一迁移到 definePageMeta 中声明。需要控制 <NuxtPage> 何时重渲染时(例如配合过渡动画),既可以在父组件上用 pageKey prop,也可以在子页面里用 definePageMetakey

<template>
  <div>
    <h1>I am the parent view</h1>
    <NuxtPage :page-key="route => route.fullPath" />
  </div>
</template>
<script setup lang="ts">
definePageMeta({
  key: route => route.fullPath,
})
</script>

keepalive 同理:需要保留父路由跨子路由变化的状态时,使用 <NuxtPage keepalive />,或通过 definePageMeta({ keepalive: { exclude: [...] } }) 把 props 透传给 Vue 的 <KeepAlive> 组件(可选参数的完整列表见 Special Metadata 一节)。

页面与布局过渡动画迁移

Nuxt 2 中把过渡动画写在组件选项里:

<script>
export default {
  transition: 'page', // or { name: 'page' }
}
</script>

Nuxt 3/4 中改用 definePageMeta 声明:

<script setup lang="ts">
definePageMeta({
  transition: {
    name: 'page',
  },
})
</script>

迁移后还需注意两点 Vue 3 层面的变化:

  • Vue 3 中过渡的 CSS 类名已从 -enter/-leave 更名为 -enter-from/-leave-to(即 page-enter-frompage-leave-to),需要同步更新样式选择器;
  • Nuxt 2 中 <Nuxt> 上的 style prop 不再适用于 <slot /> 上的过渡,相关样式请移到 -active 类中。更完整的过渡配置(layoutTransitionpageTransition、如何在 nuxt.config 中设置默认值)参见 过渡指南

动态路由与 definePageMeta 的综合迁移示例

下面这组对照完整覆盖了嵌套路由 + key + keep-alive + transition 的组合迁移,是 Nuxt 2 复杂父页面最常见的形态:

<template>
  <div>
    <NuxtChild
      keep-alive
      :keep-alive-props="{ exclude: ['modal'] }"
      :nuxt-child-key="$route.slug"
    />
  </div>
</template>

<script>
export default {
  transition: 'page', // or { name: 'page' }
}
</script>
<template>
  <div>
    <NuxtPage />
  </div>
</template>

<script setup lang="ts">
// 该编译宏在 <script> 与 <script setup> 中均可使用
definePageMeta({
  // 也可以传字符串或 computed 属性
  key: route => route.slug,
  transition: {
    name: 'page',
  },
  keepalive: {
    exclude: ['modal'],
  },
})
</script>

这组对照展示了迁移的核心心法:模板出口统一用 <NuxtPage />,而 key/keepalive/transition 全部收拢进 definePageMeta 的元信息对象。从源码看,definePageMeta 是编译宏而非普通函数,它会被编译期处理并从组件中抽出(其可识别键集合与静态抽取逻辑定义于 pages/utils.ts,运行时宏定义于 pages/runtime/composables.ts);因此传入的元数据不能引用组件实例,但可以引用导入的绑定与本地定义的纯函数。


<NuxtLink> 组件迁移

<NuxtLink>(组件文档见 4.nuxt-link.md)的大部分语法与能力保持不变,但有两个变化:

  1. 如果此前使用 <NLink> 缩写形式,请全部替换为 <NuxtLink>
  2. <NuxtLink> 现在可以直接替换所有链接——包括外部链接。官方还支持通过扩展它来提供自定义链接组件。
<template>
  <NuxtLink to="/">Home page</NuxtLink>
</template>

程序化导航:this.$router.pushnavigateTo()

Nuxt 2 通过组件实例上的 this.$router(底层 Vue Router 实例)做程序化跳转;Nuxt 3/4 提供全局可用的 navigateTo() 工具函数(navigate-to.md),它接受一个路由对象或路径字符串以及可选参数,并返回 Promise 形式的导航结果。

⚠️ 重要:必须始终 await navigateTo,或通过 return 把它链式返回(例如从事件处理器或路由中间件中返回),否则导航结果可能被吞掉。

<script>
export default {
  methods: {
    navigate () {
      this.$router.push({
        path: '/search',
        query: {
          name: 'first name',
          type: '1',
        },
      })
    },
  },
}
</script>
<script setup lang="ts">
function navigate () {
  return navigateTo({
    path: '/search',
    query: {
      name: 'first name',
      type: '1',
    },
  })
}
</script>

在源码 router.ts 中可以读到 navigateTo 的完整语义:它可以在服务端与客户端、页面与中间件与插件中等各种上下文调用;to 缺省时回退到 /;支持字符串路径与路由对象两种形式,路由对象会经由 useRouter().resolve 解析。仓库还预置了针对外部导航/脚本协议的诊断错误(如 NUXT_E2001NUXT_E2002),并支持 options.open 以新窗口打开链接。

useRouteuseRouter 替代 this.$route / this.$router

如果正在使用 Composition API,还应把组件内对 this.$routethis.$router 的访问替换为组合式函数 useRouteuseRouter(见 use-route.mduse-router.md):

<script setup lang="ts">
const route = useRoute()
const router = useRouter()

// 读取当前参数
console.log(route.params.id)

// 需要直接访问 router 实例时
router.push('/search')
</script>

两者的实现同样位于 router.tsuseRouter 定义于第 31 行附近、useRoute 于第 58 行附近),它们在客户端与服务端(SSR)都能安全调用。


迁移自查清单

按官方迁移顺序汇总为一份可执行清单(对照原迁移文档 6.pages-and-layouts.md):

  1. 入口:创建 app.vue,把需要启动一次的逻辑放入;仅单布局时把布局骨架移入。
  2. 布局:把每个布局里的 <Nuxt /> 替换为 <slot />
  3. 布局选择:用 definePageMeta({ layout: 'custom' }) 替代组件选项 layout,并确保布局名 kebab-case。
  4. 错误页:把 ~/layouts/_error.vue 移动到 ~/error.vue,需要布局时在内部包 <NuxtLayout>
  5. 动态路由:重命名 _id[id]_.vue[...slug].vue,并同步修改 params 读取键。
  6. 嵌套路由:把 <Nuxt><NuxtChild> 全部换成 <NuxtPage />
  7. 页面元信息keykeepalivetransition 等从组件选项迁移到 definePageMeta
  8. 链接与跳转<NLink><NuxtLink>this.$router.push(...)await navigateTo(...)
  9. Composition API 化this.$route/this.$routeruseRoute()/useRouter()

迁移完成后,可以继续对照 组件选项迁移文档 处理 asyncData/fetchheadvalidatescrollToTop 等其余组件选项,这些内容同样以 definePageMeta 或组合式函数作为新的落点。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388