Nuxt.js动态路由 #

一、动态路由概述 #

动态路由允许在路由路径中使用参数,使一个页面可以匹配多种 URL。Nuxt.js 使用方括号语法来定义动态路由参数。

二、基本动态路由 #

2.1 创建动态路由 #

使用 [参数名] 创建动态路由:

text
pages/
├── users/
│   └── [id].vue       → /users/:id
└── blog/
    └── [slug].vue     → /blog/:slug

2.2 获取路由参数 #

创建 pages/users/[id].vue

vue
<template>
  <div>
    <h1>用户详情</h1>
    <p>用户 ID: {{ userId }}</p>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const userId = route.params.id

const { data: user } = await useFetch(`/api/users/${userId}`)
</script>

2.3 响应式参数 #

vue
<script setup lang="ts">
const route = useRoute()
const userId = computed(() => route.params.id)

watch(userId, (newId) => {
  console.log('用户 ID 变化:', newId)
})
</script>

三、多参数路由 #

3.1 多个动态参数 #

text
pages/
└── users/
    └── [id]/
        └── posts/
            └── [postId].vue   → /users/:id/posts/:postId
vue
<template>
  <div>
    <h1>文章详情</h1>
    <p>用户 ID: {{ userId }}</p>
    <p>文章 ID: {{ postId }}</p>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const userId = route.params.id
const postId = route.params.postId
</script>

3.2 混合静态和动态路径 #

text
pages/
└── blog/
    ├── index.vue              → /blog
    ├── [category]/
    │   └── [slug].vue         → /blog/:category/:slug
    └── tag/
        └── [tag].vue          → /blog/tag/:tag

四、可选参数 #

4.1 可选动态参数 #

使用双方括号创建可选参数:

text
pages/
└── [[lang]]/
    └── index.vue              → / 或 /:lang
vue
<template>
  <div>
    <h1>首页</h1>
    <p>语言: {{ lang || '默认语言' }}</p>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const lang = route.params.lang
</script>

4.2 可选参数的应用场景 #

  • 多语言支持
  • 可选的分类筛选
  • 可选的排序参数

五、Catch-all路由 #

5.1 创建Catch-all路由 #

使用 [...参数名] 创建匹配所有层级的路由:

text
pages/
└── docs/
    └── [...slug].vue          → /docs/* 或 /docs/a/b/c
vue
<template>
  <div>
    <h1>文档页面</h1>
    <p>路径: {{ slugPath }}</p>
  </div>
</template>

<script setup lang="ts">
const route = useRoute()
const slugPath = computed(() => {
  const slug = route.params.slug
  return Array.isArray(slug) ? slug.join('/') : slug
})
</script>

5.2 Catch-all路由的应用场景 #

  • 文档系统
  • CMS 页面
  • 404 兜底路由

5.3 可选Catch-all #

使用 [[...参数名]] 创建可选的 Catch-all:

text
pages/
└── docs/
    └── [[...slug]].vue        → /docs 或 /docs/*

六、路由匹配优先级 #

6.1 匹配规则 #

Nuxt.js 按照以下优先级匹配路由:

  1. 静态路由优先
  2. 动态路由次之
  3. Catch-all 路由最后
text
pages/
├── blog/
│   ├── new.vue                → /blog/new (优先匹配)
│   └── [id].vue               → /blog/:id (其次匹配)

6.2 匹配示例 #

URL 匹配的路由
/blog/new blog/new.vue
/blog/123 blog/[id].vue
/blog/abc blog/[id].vue

七、路由验证 #

7.1 参数验证 #

使用 definePageMeta 验证路由参数:

vue
<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    const id = route.params.id
    return /^\d+$/.test(id as string)
  }
})
</script>

7.2 异步验证 #

vue
<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    const slug = route.params.slug as string
    const { data } = await useFetch(`/api/validate-slug/${slug}`)
    return data.value?.valid ?? false
  }
})
</script>

7.3 验证失败处理 #

验证失败时,Nuxt 会显示 404 页面。可以自定义错误处理:

vue
<script setup lang="ts">
definePageMeta({
  validate: async (route) => {
    const id = parseInt(route.params.id as string)
    if (isNaN(id) || id < 1) {
      throw createError({
        statusCode: 404,
        message: '用户不存在'
      })
    }
    return true
  }
})
</script>

八、动态路由与数据获取 #

8.1 监听参数变化 #

vue
<script setup lang="ts">
const route = useRoute()
const userId = computed(() => route.params.id)

const { data: user, refresh } = await useFetch(
  () => `/api/users/${userId.value}`,
  {
    watch: [userId]
  }
)
</script>

8.2 参数变化时重新获取数据 #

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

const { data: article, pending } = await useAsyncData(
  () => `article-${route.params.slug}`,
  () => $fetch(`/api/articles/${route.params.slug}`)
)
</script>

九、动态路由最佳实践 #

9.1 参数命名规范 #

  • 使用有意义的参数名:[id][slug][category]
  • 避免使用单个字符:[x]
  • 保持命名一致性

9.2 类型安全 #

vue
<script setup lang="ts">
interface RouteParams {
  id: string
  slug?: string
}

const route = useRoute()
const params = route.params as RouteParams
</script>

9.3 参数验证 #

始终验证用户输入的参数:

vue
<script setup lang="ts">
const route = useRoute()
const userId = computed(() => {
  const id = route.params.id
  const numId = Number(id)
  return isNaN(numId) ? null : numId
})

if (!userId.value) {
  throw createError({
    statusCode: 400,
    message: '无效的用户 ID'
  })
}
</script>

十、完整示例 #

10.1 博客文章详情页 #

text
pages/
└── blog/
    ├── index.vue              → /blog
    ├── [category]/
    │   ├── index.vue          → /blog/:category
    │   └── [slug].vue         → /blog/:category/:slug
    └── [...catchall].vue      → /blog/* (404处理)

pages/blog/[category]/[slug].vue

vue
<template>
  <article v-if="article">
    <header>
      <h1>{{ article.title }}</h1>
      <p class="meta">
        <span>分类: {{ category }}</span>
        <span>发布于: {{ article.date }}</span>
      </p>
    </header>
    <div class="content" v-html="article.content" />
    <footer>
      <NuxtLink :to="`/blog/${category}`">
        返回 {{ categoryName }}列表
      </NuxtLink>
    </footer>
  </article>
  <div v-else class="error">
    <h1>文章不存在</h1>
    <NuxtLink to="/blog">返回博客首页</NuxtLink>
  </div>
</template>

<script setup lang="ts">
interface Article {
  title: string
  content: string
  date: string
  category: string
}

const route = useRoute()
const category = route.params.category as string
const slug = route.params.slug as string

definePageMeta({
  validate: async (route) => {
    const validCategories = ['tech', 'life', 'tutorial']
    return validCategories.includes(route.params.category as string)
  }
})

const { data: article } = await useFetch<Article>(
  `/api/blog/${category}/${slug}`
)

const categoryName = computed(() => {
  const names: Record<string, string> = {
    tech: '技术',
    life: '生活',
    tutorial: '教程'
  }
  return names[category] || category
})

useHead({
  title: article.value ? `${article.value.title} - 博客` : '文章不存在'
})
</script>

十一、总结 #

本章介绍了 Nuxt.js 动态路由:

  • 使用 [参数名] 创建动态路由
  • 使用 [[参数名]] 创建可选参数
  • 使用 [...参数名] 创建 Catch-all 路由
  • 使用 definePageMeta 验证路由参数
  • 监听参数变化重新获取数据

动态路由是构建复杂应用的关键,下一章我们将学习路由中间件。

最后更新:2026-03-28