Nuxt 生态 精选推荐

Nuxt 3 路由系统深度讲解

HTMLPAGE 团队
12 分钟阅读

完整讲解 Nuxt 3 的自动路由、动态路由、路由中间件、导航守卫等高级特性。包含路由参数、嵌套路由、路由钩子等实战例子。

#Nuxt 3 #路由 #文件系统 #动态路由 #导航守卫

Nuxt 3 路由系统深度讲解

Nuxt 的自动路由是其最强大的特性之一。一个文件对应一个页面,无需手动配置,大大提升开发效率。

1. 文件系统路由基础

自动路由约定

pages/
├── index.vue                    → /
├── about.vue                    → /about
├── products.vue                 → /products
│
├── blog/
│   ├── index.vue               → /blog
│   └── [id].vue                → /blog/:id
│
├── admin/
│   ├── dashboard.vue           → /admin/dashboard
│   └── users.vue               → /admin/users
│
└── [...slug].vue               → /* (catch-all)

路由优先级 (Route Priority)

优先级顺序 (从高到低):
1. 静态路由     /about.vue → /about (最精确)
2. 动态路由     /posts/[id].vue → /posts/123
3. 嵌套路由     /posts/[id].vue → /posts/123
4. Catch-all    /[...slug].vue → /任何未匹配

示例匹配过程:
访问 /products/electronics/phones

routes 中的匹配顺序:
├─ /products.vue ✗
├─ /products/[category].vue ✗ (缺少 /phones)
├─ /products/[category]/[item].vue ✓ 匹配!
└─ /[...slug].vue (backup)

2. 动态路由详解

单个动态参数

<!-- pages/posts/[id].vue -->
<script setup lang="ts">
// 自动获取路由参数
const route = useRoute()
const id = route.params.id  // String

// 响应式获取参数
const { data: post } = await useFetch(`/api/posts/${id}`)
</script>

<template>
  <div>
    <h1>{{ post?.title }}</h1>
    <p>ID: {{ id }}</p>
  </div>
</template>

<!-- 访问 /posts/123 时:
     - id = "123"
     - post 包含该文章内容
-->

多个动态参数

<!-- pages/[username]/posts/[postId].vue -->
<script setup lang="ts">
const route = useRoute()
const username = route.params.username
const postId = route.params.postId

// 类型安全
const { data } = await useFetch(
  `/api/users/${username}/posts/${postId}`
)
</script>

<!-- 访问 /john/posts/42 时:
     - username = "john"
     - postId = "42"
     匹配路径: /[username]/posts/[postId].vue
-->

可选参数 (Optional Parameters)

pages/
├── posts/
│   ├── [[id]].vue           → /posts, /posts/123
│   └── [category]/[[tag]].vue → /electronics, /electronics/phones
<!-- pages/posts/[[id]].vue -->
<script setup lang="ts">
const route = useRoute()

// 如果 id 为空,则为 undefined 而非 null
const id = route.params.id || 'latest'

const postId = id === 'latest' ? 'LATEST_POST' : id
const { data: post } = await useFetch(`/api/posts/${postId}`)
</script>

<!-- 可用路由:
     /posts           → id = undefined
     /posts/latest    → id = "latest"
     /posts/123       → id = "123"
-->

Catch-all 路由

pages/
└── [...slug].vue        → 捕获所有未匹配的路由
<!-- pages/[...slug].vue -->
<script setup lang="ts">
const route = useRoute()

// slug 是一个数组
const slug = route.params.slug  // string[]
const path = slug.join('/')

// 例子:
// /docs/guides/installation → slug = ['docs', 'guides', 'installation']
// /404/missing/page → slug = ['404', 'missing', 'page']
</script>

<template>
  <div v-if="slug && slug.length > 0">
    <h1>页面: {{ path }}</h1>
  </div>
  <div v-else>
    <h1>404 - 页面未找到</h1>
  </div>
</template>

3. 嵌套路由和布局

文件结构对应的嵌套路由

pages/
├── parent/
│   ├── parent.vue          → /parent (父级路由)
│   ├── child1.vue          → /parent/child1
│   └── child2.vue          → /parent/child2

使用布局分组 (Grouped Routes)

pages/
├── layouts/
│   ├── admin.vue           (管理后台布局)
│   └── public.vue          (公开页面布局)
│
├── admin/
│   ├── index.vue           → /admin
│   ├── users.vue           → /admin/users
│   └── settings.vue        → /admin/settings
│
└── public/
    ├── about.vue           → /public/about
    └── contact.vue         → /public/contact

4. 路由导航

编程式导航

const router = useRouter()
const route = useRoute()

// 简单导航
router.push('/about')

// 对象语法
router.push({
  path: '/posts/123',
  query: { sort: 'date', limit: 10 }
  // 结果: /posts/123?sort=date&limit=10
})

// 命名路由 (通过 defineRouteRules)
router.push({
  name: 'post',
  params: { id: 123 }
})

// 相对导航
router.push('../parent')  // 相对于当前路由

// 返回
router.back()
router.go(-1)

// 替换当前历史记录
router.replace('/home')

// 检查是否在特定路由
const isHomePage = route.path === '/'
const isAboutPage = route.name === 'about'

声明式导航

<template>
  <!-- 基础链接 -->
  <NuxtLink to="/about">关于</NuxtLink>

  <!-- 对象语法 -->
  <NuxtLink :to="{ path: '/posts', query: { sort: 'date' } }">
    文章
  </NuxtLink>

  <!-- 动态链接 -->
  <NuxtLink :to="`/posts/${id}`">{{ title }}</NuxtLink>

  <!-- 活跃状态样式 -->
  <NuxtLink
    to="/admin"
    active-class="bg-blue-500"
    exact-active-class="text-bold"
  >
    管理后台
  </NuxtLink>
</template>

5. 路由参数验证

验证动态参数

// utils/validateParams.ts
export function validatePostId(id: string): boolean {
  return /^\d+$/.test(id)  // 必须是纯数字
}

export function validateUsername(username: string): boolean {
  return /^[a-zA-Z0-9_-]{3,20}$/.test(username)  // 3-20 个字母数字
}
<!-- pages/posts/[id].vue -->
<script setup lang="ts">
import { validatePostId } from '~/utils/validateParams'

const route = useRoute()
const id = route.params.id as string

// 验证
if (!validatePostId(id)) {
  throw createError({
    statusCode: 400,
    statusMessage: '无效的文章 ID'
  })
}

const { data: post } = await useFetch(`/api/posts/${id}`)
</script>

6. 路由钩子和守卫

全局前置守卫

// app.vue or nuxt.config.ts
export default defineNuxtConfig({
  // 导航守卫
  hooks: {
    // 路由改变前
    'router:beforeEach': (to, from) => {
      console.log(`从 ${from.path} 导航到 ${to.path}`)
      
      // 可以返回 false 来阻止导航
      // if (!isAuthenticated && to.path === '/admin') {
      //   return false
      // }
    },
    
    // 路由改变后
    'router:afterEach': (to, from) => {
      console.log('导航完成')
    }
  }
})

中间件守卫

// middleware/auth.ts
export default defineRouteMiddleware((to, from) => {
  const authStore = useAuthStore()
  
  // 如果未认证,重定向到登录
  if (!authStore.isAuthenticated) {
    return navigateTo('/login')
  }
})

// middleware/admin.ts
export default defineRouteMiddleware((to, from) => {
  const user = useCurrentUser()
  
  // 如果不是管理员,重定向
  if (user.value?.role !== 'admin') {
    throw createError({
      statusCode: 403,
      statusMessage: '禁止访问'
    })
  }
})

在页面中使用中间件

<!-- pages/admin/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
  middleware: ['auth', 'admin']
})

// 该页面会在加载前检查认证和权限
</script>

7. 查询字符串和 URL 状态

获取和管理查询参数

const route = useRoute()
const router = useRouter()

// 读取查询参数
const page = route.query.page || '1'
const sort = route.query.sort || 'date'
const filters = route.query.filters  // 可能是 string 或 string[]

// 设置查询参数
router.push({
  path: route.path,
  query: {
    page: '2',
    sort: 'price',
    filters: ['electronics', 'books']
  }
})

// 更新单个查询参数
router.push({
  query: {
    ...route.query,
    page: '2'
  }
})

// 清除查询参数
router.push({
  path: route.path
})

查询参数与状态同步

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

const filters = ref<string[]>()

// 从 URL 初始化
onMounted(() => {
  filters.value = Array.isArray(route.query.filters)
    ? route.query.filters
    : route.query.filters ? [route.query.filters] : []
})

// 监听过滤器变化,更新 URL
watch(filters, (newFilters) => {
  router.push({
    query: {
      ...route.query,
      filters: newFilters
    }
  })
}, { deep: true })
</script>

<template>
  <!-- 用户修改过滤器时,URL 会自动更新 -->
  <div>
    <label v-for="option in filterOptions" :key="option">
      <input
        type="checkbox"
        :value="option"
        :checked="filters?.includes(option)"
        @change="e => {
          if (e.target.checked) {
            filters = [...(filters || []), option]
          } else {
            filters = filters?.filter(f => f !== option) || []
          }
        }"
      />
      {{ option }}
    </label>
  </div>
</template>

8. 高级路由配置

自定义路由规则

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // 预渲染 (生成静态 HTML)
    '/': { prerender: true },
    '/about': { prerender: true },
    '/blog/**': { prerender: true },
    
    // 缓存策略
    '/api/**': { cache: { maxAge: 60 * 10 } },  // 缓存 10 分钟
    '/static/**': { cache: { maxAge: 60 * 60 * 24 } },  // 缓存 1 天
    
    // 重定向
    '/old-page': { redirect: '/new-page' },
    
    // 不索引
    '/admin/**': { noindex: true },
    
    // 自定义标头
    '/secure/**': { headers: { 'X-Custom-Header': 'value' } }
  }
})

路由懒加载

// Nuxt 3 默认对所有页面进行代码分割
// 不需要手动配置,自动实现:

// pages/posts/[id].vue
// ↓
// 构建时自动生成 posts-[id].chunk.js

// 按需加载时:
// /posts/1 → 加载 posts-[id].chunk.js
// /posts/2 → 复用已加载的 chunk

9. 路由最佳实践

清单

// ✅ 最佳实践

// 1. 使用文件系统路由
// ✅ 好
pages/
  └── products/[id].vue

// ❌ 避免
// 手动在 router.js 中配置路由

// 2. 有意义的参数名
// ✅ 好
pages/[username]/[postId].vue

// ❌ 避免
pages/[a]/[b].vue

// 3. 使用中间件处理认证
// ✅ 好
middleware/auth.ts
definePageMeta({ middleware: 'auth' })

// ❌ 避免
// 在每个页面中手动检查认证

// 4. 利用查询参数保持状态
// ✅ 好
/products?sort=price&page=2

// ❌ 避免
// 存储在组件本地状态,刷新时丢失

// 5. 合理的嵌套深度
// ✅ 好
/users/john/posts/42

// ❌ 避免
// /a/b/c/d/e/f/g (太深)

总结

Nuxt 3 路由系统的核心优势:

特性优势使用场景
文件系统路由零配置所有应用
自动代码分割性能优化大型应用
中间件系统灵活守卫认证/权限
查询参数同步保持状态过滤/搜索
路由规则完整控制高级场景

相关资源