3、路由系统详解:文件路由、动态路由、嵌套路由、路由中间件、路由验证

路由,说白了就是 URL 和页面组件的映射关系。Nuxt3 的路由系统非常强大,而且几乎不需要手动配置。你只需要在 pages/ 目录下放文件,路由就自动生成了。我刚开始用的时候也觉得有点神奇,但用久了你会发现,这才是现代前端框架该有的样子。

3.1 文件路由:约定大于配置

Nuxt3 采用文件系统路由。什么意思呢?就是你的文件路径,直接决定了 URL 路径。

举个例子:

pages/
├── index.vue        →  /
├── about.vue        →  /about
├── blog/
│   ├── index.vue    →  /blog
│   └── post.vue     →  /blog/post
└── user/
    └── profile.vue  →  /user/profile

看到没?pages/about.vue 自动对应 /about 路由。pages/blog/index.vue 对应 /blog。完全不需要你写 router 配置。

我的习惯: 我会把首页放在 pages/index.vue,而不是 pages/home.vue。因为 Nuxt 默认把 index.vue 当作目录的入口页面。

3.2 动态路由:处理不确定的路径

实际项目中,我们经常需要处理动态参数。比如用户详情页 /user/123,文章详情页 /article/hello-world。这时候就需要动态路由。

在 Nuxt3 中,用 [] 方括号表示动态参数:

pages/
├── user/
│   └── [id].vue        →  /user/:id
└── article/
    └── [slug].vue      →  /article/:slug

在组件中获取参数:

<script setup>
const route = useRoute()
console.log(route.params.id)   // 如果是 /user/123,输出 123
console.log(route.params.slug) // 如果是 /article/hello,输出 hello
</script>

还可以有多层动态路由:

pages/
└── category/
    └── [category]/
        └── [id].vue    →  /category/:category/:id
我曾经踩过的坑: 动态路由的参数名不要和查询参数(query)混淆。route.params.id 是路径参数,route.query.id 是 URL 问号后面的参数。两者完全不同。

3.3 嵌套路由:页面里套页面

嵌套路由适合做「父页面 + 子页面」的布局。比如后台管理系统,左侧菜单固定,右侧内容根据路由切换。

实现方式很简单:在父级目录下放一个 parent.vue,然后在同目录下建一个同名文件夹,里面放子页面。

pages/
├── dashboard.vue          →  /dashboard
└── dashboard/
    ├── index.vue          →  /dashboard (默认子路由)
    ├── settings.vue       →  /dashboard/settings
    └── users.vue          →  /dashboard/users

dashboard.vue 中,必须加上 <NuxtPage /> 组件,子页面才会渲染:

<template>
  <div>
    <h1>仪表盘</h1>
    <aside>
      <!-- 左侧菜单 -->
      <NuxtLink to="/dashboard/settings">设置</NuxtLink>
      <NuxtLink to="/dashboard/users">用户管理</NuxtLink>
    </aside>
    <main>
      <NuxtPage />  <!-- 子页面在这里渲染 -->
    </main>
  </div>
</template>

我个人习惯把 <NuxtPage /> 放在 <transition> 里,这样切换子页面时还能加个动画效果。

3.4 路由中间件:守卫你的页面

路由中间件,说白了就是在进入页面之前执行一段逻辑。最常见的场景就是「登录验证」——没登录就跳转到登录页。

Nuxt3 的中间件放在 middleware/ 目录下:

middleware/
├── auth.ts          → 全局中间件
└── guest.ts         → 命名中间件

写一个简单的 auth 中间件:

// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const token = useCookie('token')
  
  if (!token.value) {
    // 没登录,跳转到登录页
    return navigateTo('/login')
  }
})

使用方式有两种:

  • 全局中间件:文件名以 .global.ts 结尾,所有路由都会触发
  • 命名中间件:在页面组件中手动指定

在页面中指定中间件:

<script setup>
definePageMeta({
  middleware: 'auth'  // 对应 middleware/auth.ts
})
</script>
避坑指南: 我曾经在中间件里直接修改了 to.path,结果页面跳转乱了。正确的做法是用 navigateTo()abortNavigation(),不要手动修改路由对象。

3.5 路由验证:把不合法请求挡在门外

路由验证和中间件有点像,但更专注于「参数合法性」的检查。比如动态路由 /user/[id],如果 id 不是数字,直接返回 404。

在页面组件中使用 validate 属性:

<script setup>
definePageMeta({
  validate: (route) => {
    // 验证 id 是否为数字
    return /^\d+$/.test(route.params.id)
  }
})
</script>

如果验证失败,Nuxt3 会自动返回 404 页面。你也可以自定义错误处理:

definePageMeta({
  validate: (route) => {
    if (!/^\d+$/.test(route.params.id)) {
      throw createError({ statusCode: 404, message: '用户不存在' })
    }
    return true
  }
})

你想想看,如果没有路由验证,用户输入 /user/abc,后端接口可能直接报 500 错误。有了验证,404 页面优雅地告诉用户「你找的人不存在」。用户体验完全不一样。

3.6 实战建议:路由设计的最佳实践

场景 推荐做法 不推荐做法
静态页面 直接放 pages/ 手动写 router 配置
动态参数 [param].vue 用查询参数传 ID
权限控制 用路由中间件 在每个页面里写 if 判断
参数校验 validate 在组件 mounted 里校验
我的建议: 路由设计一定要扁平化。不要超过 3 层嵌套,否则 URL 会变得又长又丑,而且不利于 SEO。比如 /user/123/profile/settings 这种,完全可以拆成 /user/123/settings 两个独立路由。

嗯,路由系统就讲到这里。记住一句话:文件即路由,约定大于配置。你只要把文件放对位置,Nuxt3 会自动帮你搞定一切。下一章我们聊聊数据获取,那才是服务端渲染的重头戏。