Skip to content

路由导航

<NuxtLink> 是 Nuxt 中页面间导航的核心组件,基于 Vue Router 的 <RouterLink>

vue
<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> 标签做内部导航。

属性类型默认值说明
tostring | object-目标路由
hrefstring-to 的别名
externalboolean自动检测是否为外部链接
targetstring-_blank_self
relstring-链接关系
noRelbooleanfalse不添加 rel 属性
prefetchbooleantrue是否预取页面
noPrefetchbooleanfalse禁用预取

预取行为

默认情况下,<NuxtLink> 会在链接进入视口时自动预取目标页面的代码和数据:

vue
<!-- 禁用预取(适合指向重页面的链接) -->
<NuxtLink to="/heavy-page" no-prefetch>重页面</NuxtLink>

预取是怎么工作的?

  1. 用户打开页面,看到导航链接
  2. 链接进入浏览器视口(用户能看到的地方)
  3. Nuxt 在后台预下载该页面的 JS 和数据
  4. 用户点击链接时,页面瞬间显示(因为已经预下载了)

什么时候禁用预取?

  • 指向重页面的链接(预取会消耗带宽)
  • 页面有很多链接(预取太多浪费资源)
  • 用户不太可能点击的链接

可以全局配置预取行为:

ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
useAsyncData: { dedupe: 'defer' },
},
},
})

useRouter

<script setup> 中使用编程式导航:

ts
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

获取当前路由信息:

ts
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.paramsroute.query 等会自动更新。但如果你的 useFetch 依赖这些值,应该用 watch 选项而不是手动监听

route.params vs route.query 的区别

特性paramsquery
位置/user/:id/user?id=1
定义路由文件名决定URL 中 ? 后面
类型string | string[]string | string[]
是否必须取决于路由定义永远可选
典型用途资源标识(用户 ID、文章 slug)过滤、分页、搜索词

Nuxt 提供的导航函数,SSR 安全

ts
// 简单导航
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')
  }
})
特性navigateTorouter.push
SSR 安全✅ 服务端和客户端都能用❌ 仅客户端
中间件中使用
外部链接
新窗口
返回值RouteLocationRaw | voidPromise

推荐在所有场景下使用 navigateTo

它是 SSR 安全的,在中间件和服务端代码中也能正常工作。router.push 只在纯客户端代码中使用。

INFO

在中间件中使用 navigateTo 的常见模式

ts
// ✅ 正确:return navigateTo()
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) {
return navigateTo('/login')  // 必须有 return!
}
})

// ❌ 错误:忘记 return
export default defineNuxtRouteMiddleware((to) => {
if (!isAuthenticated()) {
navigateTo('/login')  // 导航不会生效!
}
})

中间件的返回值决定了导航行为,必须 return navigateTo()

导航守卫

全局前置守卫(插件中)

ts
// 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/)比全局守卫更好用 ——中间件可以精确控制作用范围。

页面级守卫

vue
<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 选项可以自动响应参数变化:

ts
const route = useRoute()
const { data } = await useFetch(`/api/users/${route.params.id}`, {
watch: [() => route.params.id],  // id 变化时自动重新获取
})

路由历史

ts
const router = useRouter()

router.go(1)     // 前进
router.go(-1)    // 后退
router.back()    // 后退(等价于 go(-1))
router.forward() // 前进(等价于 go(1))

注意

这些方法只在客户端有效。在 SSR 环境中,路由历史不存在。

知识脉络

text
文件路由 → 你在这里:路由导航

              ├─→ 下一步:路由中间件

              └─→ 相关:路由守卫

基于 Nuxt 4 官方文档整理编写