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 按照以下优先级匹配路由:
- 静态路由优先
- 动态路由次之
- 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