首页 / Nuxt 4 入门教程 / 动态路由与参数

Nuxt 4 入门教程

动态路由与参数

本教程共 50 篇 · 第 17 篇 · 更新于 2026-08-08 · 约 5 分钟阅读

NuxtNuxt4动态路由路由参数useRoute

本节目标:掌握用方括号定义动态路由,能读取 URL 里的参数,并理解可选参数与全匹配路由。

17-1

第 13 章的 /about/index 都是「写死的」路由,一个文件对应一个固定网址。但真实网站常有成百上千个同类页面:商品详情 /product/1/product/2,文章 /post/hello-world……你不可能为每一个都新建一个文件。

动态路由(dynamic routes)就是干这个的:用一对方括号 [ ] 把文件名或目录名包起来,那一段就会变成「通配参数」,能匹配任意值。比如 app/pages/product/[id].vue,访问 /product/1/product/2 都会命中它,12 就是 id 参数的值。

17-2

在动态页面里,通过 useRoute() 组合式函数拿到当前路由对象,参数就在 route.params 上。参数名就是你方括号里写的名字。

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

// 访问 /product/1 时,route.params.id 的值是字符串 '1'
console.log(route.params.id)
</script>

<template>
  <h1>商品编号:{{ route.params.id }}</h1>
</template>
Note

route.params.id 始终是字符串类型,即使网址里写的是数字。要做数值运算,记得用 Number(route.params.id) 转换一下,否则 '1' + 1 会得到 '11' 这种拼接结果。

你也可以在模板里直接用 $route.params.id,这是 Vue 注入的全局属性,和 useRoute() 是同一份数据,只是写法不同。

17-3

方括号不仅能单独出现,还能和普通文字拼在一起,做出「半固定」的路由。例如:

-| app/pages/
---| users-[group]/
-----| [id].vue

访问 /users-admins/123 时,模板里能同时拿到两组参数:

<template>
  <p>{{ $route.params.group }} - {{ $route.params.id }}</p>
</template>

渲染结果就是 admins - 123。这种写法在「按分组归类、又各有编号」的场景很顺手。

17-4

有时候参数可有可无。比如 //test 都想命中同一个页面,就把参数用双套方括号 [[ ]] 包起来:

-| app/pages/
---| [[slug]].vue

[[slug]].vue 能同时匹配 //testslug 在前者不存在、在后者是 'test'。注意区别:[slug].vue 只匹配有值的情况(如 /test),不匹配根路径 /

17-5

如果有一类「兜底」页面——凡是没被其它具体路由接住的网址都交给它处理,就用 [...slug].vue 这种全匹配(catch-all)写法:

<template>
  <p>{{ $route.params.slug }}</p>
</template>

访问 /hello/world 时,route.params.slug 是一个数组 ['hello', 'world']。这常用于博客、文档站做「按路径渲染内容」的通用页。

Warning

全匹配路由会把「几乎所有没匹配到的路径」都吃掉。如果只想做个 404 兜底,要小心别让它盖住了你真正的页面。通常结合第 14 章讲的 validate 做校验,或在路由表里把更具体的路由放在前面。

17-6

一条路径里可以出现多个动态段,比如 app/pages/[category]/[product].vue。访问 /book/nuxt-guide 就分别得到 category='book'product='nuxt-guide'

Nuxt 有个明确的优先级规则:具名父路由优先于嵌套动态路由。也就是说,/foo/hello 会优先匹配 pages/foo.vue,而不是 pages/foo/[slug].vue。如果你希望 /foo/foo/hello 用不同页面,正确做法是建 pages/foo/index.vuepages/foo/[slug].vue,让它们各司其职。

17-7

动态路由的方括号语法在 Nuxt 3 与 Nuxt 4 完全一致,仅目录位置从 pages/ 变为 app/pages/。可选参数([[ ]])和全匹配([...slug])也是两版通用的能力。

17-8

useRoute() 拿到的不只有 params。网址里 ? 后面的查询参数在 route.query# 后面的锚点在 route.hash。比如访问 /search?name=vue#toproute.query.name'vue'route.hash'#top'。动态段管「路径里的变量」,query 管「可选的筛选条件」,各司其职。

17-9

动态路由有时会收到不合法的参数,比如 /product/abcabc 不是数字。第 14 章提过的 validate 这时就派上用场:在页面 definePageMeta 里返回 false,Nuxt 会把它当作无效路由、直接给 404。

<script setup lang="ts">
definePageMeta({
  validate (route) {
    return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
  },
})
</script>
Tip

校验逻辑复杂时,与其塞在 validate 里,不如改用第 19 章的匿名中间件,写法更灵活,还能顺手做重定向。

17-10

动态路由最适合「同一套模板、不同数据」的页面:商品详情、用户主页、博客文章、文档章节。它的核心价值是「一套代码服务无数个类似页面」,你只管在组件里读 route.params,再去取对应数据渲染。不要把「完全不同结构」的页面硬塞进同一个动态文件,那样分支逻辑会很难维护,反而失去动态路由的简洁。

17-11

你写的 [id].vue 文件,Nuxt 在构建时会读一遍 app/pages 目录,按命名规则翻译成 Vue Router 的路由配置。核心规则就一条:方括号 [ ] 被翻译成「冒号 + 参数名」,所以 app/pages/product/[id].vue 在内部等价于路径 /product/:id

你不需要手写这份配置——Nuxt 内置的路由生成器(基于 unjs/radix3)帮你完成扫描和拼装。这也是为什么「目录结构」和「可访问网址」能始终一致:它们本来就是同一份来源,文件动了,路由表就跟着动。理解了这层,你就明白为什么加了 [id].vue 不用去任何地方「注册」它。

Note

路由生成发生在构建/启动阶段。所以动态段的命名(方括号里的词)决定了 route.params 上的键名——文件叫 [id].vue,参数键就是 id;文件叫 [slug].vue,键就是 slug。改名等于换键名,引用处要同步改。

17-12

这是动态路由最高频的坑:在 /product/1/product/2 之间切换时,Vue 为了性能会复用同一个组件实例,不会销毁重建。结果就是 <script setup> 里的初始化逻辑只跑一次;你若在里面把 route.params.id 存进一个普通变量,切到 /product/2 时那个变量不会自动更新。

正确做法是依赖 route 的响应性:route.params.id 本身是响应式的,直接在模板里用 {{ route.params.id }},或包进 computed、用 watch 监听变化:

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

// 参数变化时重新拉数据
watch(() => route.params.id, (newId) => {
  // 根据 newId 重新请求对应内容
})
</script>

这样无论从哪个商品跳到哪个商品,数据和界面都会跟着参数走,不会「卡」在第一次的值上。

17-13

再次强调:route.params 里的值永远是字符串。哪怕网址写的是 /product/42route.params.id 也是 '42'。做比较、运算前务必转换:

  • 转数字:Number(route.params.id)+'42'
  • 判断是否纯数字:用正则 /^\d+$/.test(route.params.id)(前面 validate 里就用过)。
  • 复杂数据别硬塞路径:URL 路径里只能放字符串,对象、数组请走 route.query 或单独接口获取。

一个实用组合拳:在 validate 里拦掉非数字 id,进入页面后就能放心 Number(),不用担心拿到 NaN 导致渲染崩掉。

Tip

想看参数到底长什么样?在 <script setup>console.log(route.params) 然后打开浏览器控制台,比凭空猜更靠谱。开发阶段多打几次日志,能少踩很多「以为拿到了数字其实是字符串」的坑。

17-14

方括号 [name] 让一个文件匹配一类网址,参数用 useRoute().params 读取;双括号 [[name]] 表示可选,三点 [...name] 表示全匹配。记住参数是字符串、参数变化要 watch。下章我们看「页面里再套页面」的嵌套路由。