Nuxt 页面与布局迁移指南:从 Nuxt 2 的 `<Nuxt>`/`_id` 升级到 Nuxt 3/4 的 `<NuxtPage>` 与 `definePageMeta`
本指南围绕仓库中的 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.ts、router.ts 中对应的底层实现。
迁移背景补充:在 Nuxt 3 中,整个框架基于 Vue 3 重写,官方推荐默认使用 Composition API 与 <script setup>(参见 迁移总览)。页面与布局迁移只是其中的一部分,其背后的共同主线是:凡是在 Nuxt 2 里通过组件选项(Options API)写在页面上的路由级行为,在 Nuxt 3/4 中统一改用 definePageMeta 编译宏表达。
app.vue:新的应用级中央入口
Nuxt 3 不再要求你维护 nuxt.config.js 与 layouts/default.vue 并存的旧式根结构,而是提供一个集中式入口组件 ~/app.vue(对应目录文档 3.app.md)。
- 如果你的源码目录中没有
app.vue文件,Nuxt 会使用自己内置的默认版本,因此在迁移初期不创建它应用依然能运行。 app.vue适合放置“整个应用启动时只需运行一次”的自定义逻辑,以及「每个页面都会出现」的公共组件。- 特别的,如果你只有一个布局,可以完全放弃 layouts 机制,把布局骨架直接写进
app.vue。
迁移建议
- 创建
app.vue,并把需要在应用顶层执行一次的逻辑放进其中。 - 若项目仅存在单一布局,将其模板迁移进
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)。
关键改动清单
<Nuxt />→<slot />:把布局模板中的<Nuxt />替换为<slot />。- 布局选择方式变化:改用
definePageMeta编译宏来声明页面使用的布局。 - 布局名称 kebab-case 化:
app/layouts/customLayout.vue在页面中引用时写作custom-layout。目录文档 1.layouts.md 中的命名对照表进一步印证了这套规则:~/layouts/desktop/default.vue→desktop-default,~/layouts/desktop-base/base.vue→desktop-base,~/layouts/desktop/index.vue→desktop;为清晰起见,官方建议让布局文件名与其名称一致(DesktopDefault.vue→desktop-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.vue → error.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/ 下的文件并生成路由记录,页面元信息(例如 key、transition、keepalive)会在构建期从 definePageMeta 调用中静态抽取(对应 pages/utils.ts 中的 PAGE_META_MACRO_NAMES 与运行时宏实现 pages/runtime/composables.ts)。
动态路由(Dynamic Routes)命名变化
动态路由的文件名约定与 Nuxt 2 完全不同,需要重命名 app/pages/ 下的部分文件:
- Nuxt 2 用
_id表达动态参数 → Nuxt 3/4 使用[id]。 - 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,也可以在子页面里用 definePageMeta 的 key:
<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-from、page-leave-to),需要同步更新样式选择器; - Nuxt 2 中
<Nuxt>上的styleprop 不再适用于<slot />上的过渡,相关样式请移到-active类中。更完整的过渡配置(layoutTransition、pageTransition、如何在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)的大部分语法与能力保持不变,但有两个变化:
- 如果此前使用
<NLink>缩写形式,请全部替换为<NuxtLink>。 <NuxtLink>现在可以直接替换所有链接——包括外部链接。官方还支持通过扩展它来提供自定义链接组件。
<template>
<NuxtLink to="/">Home page</NuxtLink>
</template>
程序化导航:this.$router.push → navigateTo()
Nuxt 2 通过组件实例上的 this.$router(底层 Vue Router 实例)做程序化跳转;Nuxt 3/4 提供全局可用的 navigateTo() 工具函数(navigate-to.md),它接受一个路由对象或路径字符串以及可选参数,并返回 Promise 形式的导航结果。
⚠️ 重要:必须始终
awaitnavigateTo,或通过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_E2001、NUXT_E2002),并支持 options.open 以新窗口打开链接。
用 useRoute 与 useRouter 替代 this.$route / this.$router
如果正在使用 Composition API,还应把组件内对 this.$route 与 this.$router 的访问替换为组合式函数 useRoute 与 useRouter(见 use-route.md 与 use-router.md):
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
// 读取当前参数
console.log(route.params.id)
// 需要直接访问 router 实例时
router.push('/search')
</script>
两者的实现同样位于 router.ts(useRouter 定义于第 31 行附近、useRoute 于第 58 行附近),它们在客户端与服务端(SSR)都能安全调用。
迁移自查清单
按官方迁移顺序汇总为一份可执行清单(对照原迁移文档 6.pages-and-layouts.md):
- 入口:创建
app.vue,把需要启动一次的逻辑放入;仅单布局时把布局骨架移入。 - 布局:把每个布局里的
<Nuxt />替换为<slot />。 - 布局选择:用
definePageMeta({ layout: 'custom' })替代组件选项layout,并确保布局名 kebab-case。 - 错误页:把
~/layouts/_error.vue移动到~/error.vue,需要布局时在内部包<NuxtLayout>。 - 动态路由:重命名
_id→[id]、_.vue→[...slug].vue,并同步修改params读取键。 - 嵌套路由:把
<Nuxt>与<NuxtChild>全部换成<NuxtPage />。 - 页面元信息:
key、keepalive、transition等从组件选项迁移到definePageMeta。 - 链接与跳转:
<NLink>→<NuxtLink>;this.$router.push(...)→await navigateTo(...)。 - Composition API 化:
this.$route/this.$router→useRoute()/useRouter()。
迁移完成后,可以继续对照 组件选项迁移文档 处理 asyncData/fetch、head、validate、scrollToTop 等其余组件选项,这些内容同样以 definePageMeta 或组合式函数作为新的落点。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00