页面元数据
本教程共 50 篇 · 第 14 篇 · 更新于 2026-08-08 · 约 5 分钟阅读
本节目标:学会用 definePageMeta 给每个页面设置专属元数据,理解 keepalive 如何保持页面状态。
14-1
每个页面除了「显示什么内容」,往往还需要一些「关于这个页面本身的说明」:它用哪个布局、进入前要跑什么中间件、要不要保持状态、标题叫什么。这些说明就叫页面元数据(page metadata)。
在 Nuxt 里,设置元数据统一用 definePageMeta 这个编译宏(compiler macro)。它看起来像一个函数,但和普通函数不同——它会在编译阶段被处理掉,不会出现在最终的运行时代码里。所以你不能把它当普通函数去调用或引用,只能在 <script setup> 顶层直接使用。
<script setup lang="ts">
definePageMeta({
title: '我的首页',
})
</script>
Note宏(macro)是 Vue 单文件组件编译期的一种特殊写法,比如
defineProps、defineEmits也是宏。definePageMeta同理:它只是给编译器看的「标记」,会被直接提升到组件外面变成路由信息。因此,你在definePageMeta里不能引用组件内的响应式数据或会触发副作用的函数,只能引用导入的绑定和本地定义的纯函数。
14-2
你写在 definePageMeta 里的自定义字段,会被挂到路由对象的 meta 上。在任意组件里,用 useRoute() 就能读出来:
<script setup lang="ts">
const route = useRoute()
console.log(route.meta.title) // 输出:我的首页
</script>
这一招在你做「根据页面切换全局标题」「按页面分组做权限判断」之类需求时非常有用。比如你想在导航栏里显示当前页面标题,就可以从 route.meta 里取。
Tip想要类型安全的话,可以扩展
PageMeta接口。新建一个.d.ts文件(例如项目根目录的index.d.ts),写上declare module '#app' { interface PageMeta { pageType?: string } },之后definePageMeta就能补全并校验你自定义的字段了。
14-3
keepalive 是 definePageMeta 里一个很实用的特殊字段。当设为 true 时,Nuxt 会用 Vue 的 <KeepAlive> 组件把页面包起来。效果是:你从这个页面跳走再跳回来,页面之前的运行状态(比如输入框里填的字、滚动位置、组件里的局部变量)会被保留,而不是重新从零渲染。
<script setup lang="ts">
definePageMeta({
keepalive: true,
})
</script>
这在「父路由带多个动态子路由」的场景尤其好用。举个例子,左侧是一列文章列表,右侧是文章内容。你在父页里切换不同文章(子路由变化),如果不加 keepalive,父页的列表状态可能被重置;加上 keepalive: true,切换时父页状态就被稳稳保住。
你还可以把 keepalive 写成对象,传给 <KeepAlive> 更细的参数。比如排除某个组件不被缓存:
<script setup lang="ts">
definePageMeta({
keepalive: {
exclude: ['modal'],
},
})
</script>
Warning在
<NuxtPage>上也可以直接写keepalive属性(<NuxtPage keepalive />),效果和页面里的keepalive: true类似,但它作用于父路由渲染子页时的缓存策略。两者按需选择,别重复套导致行为难预测。
14-4
除了 title 和 keepalive,definePageMeta 还认识一批有特殊用途的字段。这里先列个速览,后面对应的章节会细讲:
layout:指定这个页面用哪个布局(第 16 章)。middleware:进入页面前要跑的中间件(第 19 章)。pageTransition/layoutTransition:页面与布局的过渡动画(第 21 章)。key:控制<NuxtPage>何时重新渲染,常用于嵌套路由(第 18 章)。validate:校验当前路由是否合法,不合法可返回 404。alias:给同一个页面起多个可访问的网址别名。redirect:进入这个路由时直接跳转到别处。
<script setup lang="ts">
definePageMeta({
// 别名:/users/1 和 /u/1 都能访问到这个页面
alias: ['/u/:id'],
// 校验:id 必须是数字,否则 404
validate (route) {
return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
},
})
</script>
14-5
definePageMeta 在 Nuxt 3 与 Nuxt 4 中写法完全一致,没有目录差异。唯一要留意的是:definePageMeta 的元数据只在「默认路由文件」里读取;如果你用了第 18 章会讲到的「命名视图」(name@view.vue),在命名视图的兄弟文件里写的 meta 不会影响整条路由。
14-6
如果你的页面是嵌套路由(父子页面,见第 18 章),那么父页和子页各自 definePageMeta 里的 meta,会被 Nuxt 合并成一个对象。比如父页设了 title、keepalive,子页设了 middleware,最终 route.meta 上两者都在。这条合并规则让你能「父页管通用设置、子页管自己的设置」,互不打架。
Note合并时若父子写了同名字段,子页的值通常覆盖父页。设计多个层级的 meta 时,留意不要无意中互相覆盖。
14-7
你可能会以为,在 definePageMeta 里写了 title: '我的首页',浏览器标签页就会自动显示这个标题。其实不会。definePageMeta 里的 title(以及你写的任何自定义字段)只是被挂到路由的 meta 上,是个「数据」,并不会自动去改 <title> 标签。
这里最容易踩的坑就是:设了 title 却没看到标签页变化,然后以为写法错了。记住——definePageMeta 只负责「存」,不负责「用」。
14-8
要让标签页跟着页面变,得把 route.meta.title 喂给 useHead(或 useSeoMeta)。最常见的是在布局里统一处理一次:
<script setup lang="ts">
const route = useRoute()
// 用函数形式:标题要跟着当前路由动态变化
useHead(() => ({
title: route.meta.title as string,
}))
</script>
这样每个页面在 definePageMeta 里设的 title,就会变成浏览器标签上的文字。这里特意写成函数 () => ({...}),因为标题得随路由切换实时算,不能写死。如果你只想给某个页面单独设标签名,在该页的 <script setup> 里直接 useHead({ title: '...' }) 也行。
Tip想顺带把描述、关键词也按页面设置?把
useHead换成useSeoMeta,字段名更语义化(如description、ogTitle),对搜索引擎更友好。
14-9
前面速览里提到过 alias 和 redirect,这两个字段常被搞混,区别其实很清楚:
alias是「同一个页面,多个门牌号」:/users/1和/u/1都能进同一页,浏览器地址栏不变,内容一样。redirect是「进门就被赶去别处」:访问这个路由时,地址会被改写成目标地址,你实际看到的是另一个页面。
一句话记忆:alias 共用一套内容、URL 不改;redirect 换页面、URL 也改。
<script setup lang="ts">
definePageMeta({
// 旧网址统一引导到新址,地址栏会变成 /blog
redirect: '/blog',
})
</script>
如果你只是想给一条长网址起个短别名方便分享,选 alias;如果是旧链接废弃要导流,选 redirect。
14-10
第 14 章前面说 validate 返回 false 会给 404。其实它还能返回一个对象来「校验失败但跳走」,而不是干巴巴报 404:
<script setup lang="ts">
definePageMeta({
validate (route) {
// 不是数字就跳回列表页,比直接 404 更友好
if (!/^\d+$/.test(route.params.id as string)) {
return { statusCode: 301, redirect: '/products' }
}
return true
},
})
</script>
validate 返回 { statusCode, redirect } 时,Nuxt 会按你给的状态码做跳转。这比「一律 404」体验更好,也常和路由重定向搭配使用。
14-11
页面元数据就是用 definePageMeta 给页面贴标签。你想存自定义信息就直接写字段、用 route.meta 读;想保住页面状态就开 keepalive。title 只是数据,要显示到标签页得靠 useHead 接一下。后面几章的布局、中间件、过渡,本质上都是在 definePageMeta 里填对应字段。