路由导航
NuxtLink 组件
<NuxtLink> 是 Nuxt 中页面间导航的核心组件,基于 Vue Router 的 <RouterLink>:
<template>
<!-- 基本用法 -->
<NuxtLink to="/about">关于我们</NuxtLink>
<!-- 动态链接 -->
<NuxtLink :to="`/user/${userId}`">用户详情</NuxtLink>
<!-- 对象形式 -->
<NuxtLink :to="{ name: 'user-id', params: { id: 1 } }">用户</NuxtLink>
<!-- 外部链接(自动添加 target="_blank" rel="noopener") -->
<NuxtLink to="https://nuxt.com" external>Nuxt 官网</NuxtLink>
<!-- 带查询参数 -->
<NuxtLink :to="{ path: '/search', query: { q: 'nuxt' } }">搜索</NuxtLink>
</template><NuxtLink> vs <a> 标签 vs <RouterLink>
| 组件 | 行为 | 用途 |
|---|---|---|
<a href="..."> | 整页刷新,重新加载所有资源 | 外部链接 |
<RouterLink> | 局部更新,SPA 导航 | Vue 项目内部导航 |
<NuxtLink> | 局部更新 + 预取 + 自动检测外部链接 | Nuxt 项目导航(推荐) |
简单规则
应用内导航用 <NuxtLink>,外部链接也用 <NuxtLink external>。不要用 <a> 标签做内部导航。
NuxtLink 属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
to | string | object | - | 目标路由 |
href | string | - | to 的别名 |
external | boolean | 自动检测 | 是否为外部链接 |
target | string | - | _blank、_self 等 |
rel | string | - | 链接关系 |
noRel | boolean | false | 不添加 rel 属性 |
prefetch | boolean | true | 是否预取页面 |
noPrefetch | boolean | false | 禁用预取 |
预取行为
默认情况下,<NuxtLink> 会在链接进入视口时自动预取目标页面的代码和数据:
<!-- 禁用预取(适合指向重页面的链接) -->
<NuxtLink to="/heavy-page" no-prefetch>重页面</NuxtLink>预取是怎么工作的?
- 用户打开页面,看到导航链接
- 链接进入浏览器视口(用户能看到的地方)
- Nuxt 在后台预下载该页面的 JS 和数据
- 用户点击链接时,页面瞬间显示(因为已经预下载了)
什么时候禁用预取?
- 指向重页面的链接(预取会消耗带宽)
- 页面有很多链接(预取太多浪费资源)
- 用户不太可能点击的链接
可以全局配置预取行为:
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
useAsyncData: { dedupe: 'defer' },
},
},
})useRouter
在 <script setup> 中使用编程式导航:
const router = useRouter()
// 基本导航
router.push('/about')
// 带参数导航
router.push({ name: 'user-id', params: { id: 1 } })
// 带查询参数
router.push({ path: '/search', query: { q: 'nuxt' } })
// 替换当前历史记录(不留"后退"记录)
router.replace('/about')
// 后退
router.back()
// 前进
router.forward()router.push vs router.replace 的区别
push:添加新的历史记录,用户可以"后退"到之前的页面replace:替换当前历史记录,用户不能"后退"
什么时候用 replace?
- 登录成功后跳转(不需要后退到登录页)
- 表单提交后跳转
- 重定向场景
useRoute
获取当前路由信息:
const route = useRoute()
route.path // '/user/1' — 当前路径(不含查询参数)
route.fullPath // '/user/1?tab=profile' — 完整路径(含查询参数和哈希)
route.params // { id: '1' } — 路由参数
route.query // { tab: 'profile' } — 查询参数
route.hash // '#section' — URL 哈希
route.name // 'user-id' — 路由名称
route.matched // [...] — 匹配的路由记录
route.meta // {...} — 路由元信息
route.redirectedFrom // 重定向前路由INFO
️ route 是响应式的:当路由变化时 route.params、route.query 等会自动更新。但如果你的 useFetch 依赖这些值,应该用 watch 选项而不是手动监听
route.params vs route.query 的区别
| 特性 | params | query |
|---|---|---|
| 位置 | /user/:id | /user?id=1 |
| 定义 | 路由文件名决定 | URL 中 ? 后面 |
| 类型 | string | string[] | string | string[] |
| 是否必须 | 取决于路由定义 | 永远可选 |
| 典型用途 | 资源标识(用户 ID、文章 slug) | 过滤、分页、搜索词 |
navigateTo
Nuxt 提供的导航函数,SSR 安全:
// 简单导航
await navigateTo('/about')
// 外部链接
await navigateTo('https://nuxt.com', { external: true })
// 打开新窗口
await navigateTo('/report', { open: { target: '_blank' } })
// 替换历史记录
await navigateTo('/login', { replace: true })
// 在中间件中使用
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) {
return navigateTo('/login')
}
})navigateTo vs router.push
| 特性 | navigateTo | router.push |
|---|---|---|
| SSR 安全 | ✅ 服务端和客户端都能用 | ❌ 仅客户端 |
| 中间件中使用 | ✅ | ❌ |
| 外部链接 | ✅ | ❌ |
| 新窗口 | ✅ | ❌ |
| 返回值 | RouteLocationRaw | void | Promise |
推荐在所有场景下使用 navigateTo
它是 SSR 安全的,在中间件和服务端代码中也能正常工作。router.push 只在纯客户端代码中使用。
INFO
️ 在中间件中使用 navigateTo 的常见模式
// ✅ 正确:return navigateTo()
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) {
return navigateTo('/login') // 必须有 return!
}
})
// ❌ 错误:忘记 return
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) {
navigateTo('/login') // 导航不会生效!
}
})中间件的返回值决定了导航行为,必须 return navigateTo()。
导航守卫
全局前置守卫(插件中)
// app/plugins/navigation.ts
export default defineNuxtPlugin(() => {
const router = useRouter()
router.beforeEach((to, from) => {
console.log(`从 ${from.path} 导航到 ${to.path}`)
})
router.afterEach((to, from) => {
console.log(`导航完成:${to.path}`)
})
})beforeEach vs afterEach
beforeEach:导航发生前执行,可以阻止导航afterEach:导航完成后执行,不能阻止导航(用于日志、统计等)
大多数情况下
路由中间件(app/middleware/)比全局守卫更好用 ——中间件可以精确控制作用范围。
页面级守卫
<script setup>
// 离开当前页面前
onBeforeRouteLeave((to, from) => {
const answer = confirm('确定要离开吗?未保存的数据会丢失。')
if (!answer) return false
})
// 路由更新前(同一个组件,参数变化)
onBeforeRouteUpdate(async (to) => {
await loadData(to.params.id)
})
</script>onBeforeRouteUpdate 什么时候触发?
当从 /users/1 导航到 /users/2 时,由于是同一个组件,Vue Router 会复用组件实例。此时 onMounted 不会重新执行,你需要用 onBeforeRouteUpdate 响应参数变化。
更好的方案
如果你用的是 useFetch,配合 watch 选项可以自动响应参数变化:
const route = useRoute()
const { data } = await useFetch(`/api/users/${route.params.id}`, {
watch: [() => route.params.id], // id 变化时自动重新获取
})路由历史
const router = useRouter()
router.go(1) // 前进
router.go(-1) // 后退
router.back() // 后退(等价于 go(-1))
router.forward() // 前进(等价于 go(1))注意
这些方法只在客户端有效。在 SSR 环境中,路由历史不存在。
知识脉络
文件路由 → 你在这里:路由导航
│
├─→ 下一步:路由中间件
│
└─→ 相关:路由守卫