页面元信息
使用 definePageMeta 为页面添加元数据,控制页面行为。
为什么需要页面元信息?
不同的页面可能需要不同的配置:
| 需求 | 不用 definePageMeta | 用 definePageMeta |
|---|---|---|
| 某个页面用 admin 布局 | 在页面内手动切换布局 | layout: 'admin' |
| 某个页面需要登录 | 在页面内写判断逻辑 | middleware: 'auth' |
| 动态路由参数验证 | 在页面内验证 | validate: ... |
| 页面切换动画 | 全局统一 | 每个页面自定义 |
definePageMeta 让你在页面级别配置这些行为,而不是全局配置。
基本用法
<script setup>
definePageMeta({
layout: 'default',
middleware: 'auth',
title: '用户中心',
})
</script>definePageMeta 的特殊性
- 它在编译时被提取和处理,不在运行时执行
- 这意味着你不能使用运行时变量
- 它是一个编译器宏,不是普通函数
可用选项
layout — 指定布局
<script setup>
definePageMeta({
layout: 'admin', // 使用 app/layouts/admin.vue
})
// 或禁用布局
definePageMeta({
layout: false, // 不使用任何布局
})
</script>为什么有时要禁用布局?
登录页面通常不需要导航栏和侧边栏,设置 layout: false 后页面独立显示。
Nuxt 4 布局 Props
Nuxt 4.4+ 支持向布局传递 Props:
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({
layout: {
name: 'panel',
props: {
sidebar: true,
title: 'Dashboard',
},
},
})
</script><!-- layouts/panel.vue -->
<script setup lang="ts">
defineProps<{
sidebar?: boolean
title?: string
}>()
</script>为什么需要向布局传 Props?
以前布局是"一个模子刻出来的",所有使用同一布局的页面外观完全一样。现在你可以让不同页面在使用同一布局时,有不同的表现(如侧边栏是否显示)。
middleware — 路由中间件
<script setup>
definePageMeta({
// 单个中间件
middleware: 'auth',
// 多个中间件
middleware: ['auth', 'admin'],
// 内联中间件
middleware: [
(to, from) => {
// 中间件逻辑
},
],
})
</script>validate — 路由验证
验证动态路由参数,验证失败返回 404:
<script setup>
definePageMeta({
validate: async (route) => {
// id 必须是数字
return /^\d+$/.test(route.params.id as string)
},
})
</script>验证流程
- 用户访问
/blog/abc validate返回false- Nuxt 自动显示 404 页面
典型场景
- ID 必须是数字:
/^\d+$/.test(route.params.id) - slug 必须符合格式:
/^[a-z0-9-]+$/.test(route.params.slug) - 文章是否存在:
await checkPostExists(route.params.id)
pageTransition — 页面过渡
<script setup>
definePageMeta({
pageTransition: {
name: 'slide-left',
mode: 'out-in',
},
})
</script>layoutTransition — 布局过渡
<script setup>
definePageMeta({
layoutTransition: {
name: 'fade',
mode: 'out-in',
},
})
</script>key — 页面 key
控制页面组件是否重新渲染:
<script setup>
definePageMeta({
// 路由变化时完全重新渲染(不复用组件)
key: route => route.fullPath,
// 固定 key(所有实例共享同一组件)
key: 'static',
})
</script>什么时候需要设置 key?
- 默认情况下,同一组件的路由会复用(如
/users/1→/users/2) - 如果你希望参数变化时完全重新渲染(而不是复用),设置
key: route => route.fullPath - 这会影响性能,只在需要时使用
keepalive — 缓存页面
<script setup>
definePageMeta({
keepalive: true,
// 或带选项
keepalive: {
include: ['MyComponent'],
max: 10,
},
})
</script>keepalive 的作用
缓存组件实例,离开页面后不销毁,回来时恢复状态。
适合场景
用户在列表页翻了 3 页,点进详情页,再回来时希望还在第 3 页。
INFO
️ 注意:keepalive 会保留组件状态 包括未清理的定时器和事件监听。可能导致内存泄漏,谨慎使用
自定义元信息
你可以添加任意自定义字段,在中间件中访问:
<script setup>
definePageMeta({
requiresAuth: true,
roles: ['admin', 'editor'],
breadcrumb: [
{ label: '首页', to: '/' },
{ label: '用户管理', to: '/users' },
{ label: '详情' },
],
})
</script>在中间件中访问:
export default defineNuxtRouteMiddleware((to) => {
if (to.meta.requiresAuth) {
// 需要认证
}
if (to.meta.roles?.includes('admin')) {
// 需要管理员
}
})自定义元信息的灵活用途
requiresAuth:标记需要认证的页面roles:标记需要特定角色breadcrumb:面包屑导航数据title:页面标题(给中间件用,统一设置 SEO)activeMenu:当前激活的菜单项
类型扩展
为自定义元信息添加类型声明:
// app/types/index.d.ts
declare module '#app' {
interface PageMeta {
requiresAuth?: boolean
roles?: string[]
breadcrumb?: Array<{ label: string; to?: string }>
}
}
export {}为什么要添加类型声明?
没有 export {} 的话,文件会被当作脚本而不是模块,declare module 会污染全局类型。
注意事项
1. 只能在页面中使用
definePageMeta 只能在 pages/ 下的 .vue 文件中使用。
2. 编译时转换
definePageMeta 在编译时被提取,不在运行时执行:
<!-- ❌ 错误:不能使用运行时变量 -->
<script setup>
const myLayout = computed(() => isMobile.value ? 'mobile' : 'desktop')
definePageMeta({
layout: myLayout, // 不行,这不是编译时常量
})
</script>
<!-- ✅ 正确:在中间件中动态设置 -->
<script setup>
definePageMeta({
middleware: 'set-layout',
})
</script>// app/middleware/set-layout.ts
export default defineNuxtRouteMiddleware((to) => {
const isMobile = useIsMobile()
setPageLayout(isMobile.value ? 'mobile' : 'default')
})为什么不能使用运行时变量?
definePageMeta 的内容在编译时被提取到路由配置中。编译时没有 computed、ref 这些运行时概念,只有静态值。
如果你需要动态行为,使用中间件 + setPageLayout()。
3. 不能访问组件上下文
definePageMeta 中不能使用 useRoute、useState 等组合式函数,因为它不在组件实例中执行。
知识脉络
路由规则 → 你在这里:页面元信息
│
├─→ 相关:布局(05-视图与布局)
│
├─→ 相关:路由中间件(04-路由与导航)
│
└─→ 相关:页面过渡(05-视图与布局)