程序化路由与深链接设计
不是所有导航都通过
<NuxtLink>完成——表单提交后跳转、权限校验后重定向、条件性导航都需要程序化路由控制。本章详解useRouter/useRoute完整 API、路由历史栈管理、Query 参数设计模式,以及 AI 视频播放页的深链接实战方案。
1. useRouter / useRoute 完整 API
1.1 useRoute — 当前路由信息
useRoute() 返回当前路由的只读响应式对象:
<script setup lang="ts">
const route = useRoute()
// 常用属性
route.path // '/videos/123' — URL 路径
route.fullPath // '/videos/123?t=30' — 完整路径(含 query 和 hash)
route.params // { id: '123' } — 动态参数
route.query // { t: '30' } — 查询参数
route.hash // '#comments' — 哈希值
route.name // 'videos-id' — 路由名称
route.meta // { ... } — definePageMeta 中的自定义字段
route.matched // RouteRecord[] — 匹配的路由记录链
route.redirectedFrom // RouteLocation — 重定向来源
</script>1.2 useRouter — 路由实例
useRouter() 返回路由实例,用于程序化导航:
const router = useRouter()
// ── 导航方法 ──
router.push('/videos') // 添加历史记录
router.push({ name: 'videos-id', params: { id: '123' } })
router.replace('/videos') // 替换当前历史记录(无后退)
router.back() // 后退
router.forward() // 前进
router.go(-2) // 后退 2 步
// ── 路由信息 ──
router.currentRoute.value // 当前路由(同 useRoute())
router.getRoutes() // 获取所有已注册路由
router.hasRoute('videos-id') // 检查路由是否存在
router.resolve({ name: 'videos-id', params: { id: '123' } }) // 解析路由
// ── 导航守卫 ──
router.beforeEach((to, from) => { ... }) // 全局前置守卫
router.afterEach((to, from) => { ... }) // 全局后置守卫
router.onError((error) => { ... }) // 导航错误处理1.3 navigateTo vs router.push
Nuxt4 中推荐使用 navigateTo 而不是 router.push:
| 特性 | navigateTo | router.push |
|---|---|---|
| SSR 支持 | ✅ 服务端发送 302 重定向 | ❌ 仅客户端 |
| 中间件中可用 | ✅ | ❌(在中间件上下文中不可靠) |
| 外部链接 | ✅ { external: true } | ❌ |
| HTTP 状态码 | ✅ { redirectCode: 301 } | ❌ |
| 通用性 | ✅ 两端通用 | 仅客户端 |
// ✅ 推荐:在所有 Nuxt 上下文中都安全
navigateTo('/videos')
navigateTo({ name: 'videos-id', params: { id: '123' } })
// ⚠️ 仅在确定是客户端环境时使用
const router = useRouter()
router.push('/videos')1.4 路由守卫的最佳实践
// app/plugins/router-guard.ts
export default defineNuxtPlugin(() => {
const router = useRouter()
// 全局后置守卫:页面访问日志
router.afterEach((to, from) => {
if (import.meta.client) {
console.log(`[Navigation] ${from.path} → ${to.path}`)
}
})
// 全局错误处理
router.onError((error, to, from) => {
console.error(`[Navigation Error] ${from.path} → ${to.path}`, error)
// 网络错误时显示提示
if (error.message.includes('Failed to fetch')) {
// 显示离线提示或重试按钮
}
})
})2. 路由历史栈管理
2.1 push vs replace
// push:添加新记录到历史栈
// 用户可以点击后退回到上一页
navigateTo('/videos/123')
// replace:替换当前记录
// 用户点击后退会跳过当前页
navigateTo('/videos/123', { replace: true })何时用 replace?
// 场景 1:登录后跳转(不希望后退回到登录页)
async function handleLogin() {
await login(credentials)
navigateTo('/', { replace: true })
}
// 场景 2:URL 规范化(如 /Videos → /videos)
// 不产生额外的历史记录
navigateTo('/videos', { replace: true, redirectCode: 301 })
// 场景 3:表单提交后跳转
async function handleSubmit() {
await $fetch('/api/videos', { method: 'POST', body: formData })
navigateTo('/studio/projects', { replace: true })
}2.2 后退与前进
const router = useRouter()
// 后退一步
router.back()
// 前进一步
router.forward()
// 后退 N 步
router.go(-2)
// 自定义返回逻辑
function goBack() {
// 如果有历史记录,后退;否则回到首页
if (window.history.length > 1) {
router.back()
} else {
navigateTo('/')
}
}2.3 导航完成检测
navigateTo 和 router.push 都返回 Promise:
// 等待导航完成后执行操作
await navigateTo('/videos')
console.log('导航完成!')
// 检测导航是否成功
try {
await router.push('/videos')
} catch (error) {
if (isNavigationFailure(error, NavigationFailureType.aborted)) {
console.log('导航被中间件中止')
}
if (isNavigationFailure(error, NavigationFailureType.duplicated)) {
console.log('已经在目标页面上了')
}
}3. Query 参数设计模式
3.1 基本用法
<script setup lang="ts">
const route = useRoute()
// 读取 query 参数
const page = computed(() => Number(route.query.page) || 1)
const sort = computed(() => (route.query.sort as string) || 'newest')
const search = computed(() => (route.query.q as string) || '')
</script>3.2 更新 Query 参数
const router = useRouter()
const route = useRoute()
// 更新单个参数(保留其他参数)
function updateQuery(key: string, value: string | undefined) {
const query = { ...route.query }
if (value) {
query[key] = value
} else {
delete query[key]
}
router.push({ query })
}
// 使用示例
updateQuery('page', '2') // ?...&page=2
updateQuery('sort', 'popular') // ?...&sort=popular
updateQuery('page', undefined) // 移除 page 参数3.3 useRouteQuery Composable
封装一个通用的 Query 参数 Composable:
// app/composables/useRouteQuery.ts
export function useRouteQuery(key: string, defaultValue = '') {
const route = useRoute()
const router = useRouter()
const value = computed({
get: () => (route.query[key] as string) || defaultValue,
set: (newValue) => {
const query = { ...route.query }
if (newValue && newValue !== defaultValue) {
query[key] = newValue
} else {
delete query[key]
}
router.replace({ query })
},
})
return value
}使用时就像普通的 ref:
<script setup lang="ts">
const page = useRouteQuery('page', '1')
const sort = useRouteQuery('sort', 'newest')
// 读取
console.log(page.value) // '1'
// 修改(自动更新 URL)
page.value = '2' // URL 变为 ?page=2&sort=newest
</script>4. AI 视频播放页深链接设计
4.1 什么是深链接
深链接(Deep Link)是指 URL 不仅指向一个页面,还精确描述页面内的状态——播放时间、选中的字幕轨、画质、是否全屏等。用户可以分享 URL 让他人看到完全相同的页面状态。
4.2 URL 结构设计
/videos/abc123?t=125&quality=1080p&subtitle=cn&autoplay=1#comments
路径参数:
abc123 — 视频 ID
Query 参数:
t=125 — 播放到第 125 秒
quality=1080p — 视频画质
subtitle=cn — 字幕语言
autoplay=1 — 自动播放
Hash:
#comments — 滚动到评论区
4.3 完整实现
// app/composables/useVideoDeepLink.ts
interface VideoDeepLinkState {
time: Ref<number>
quality: Ref<string>
subtitle: Ref<string>
autoplay: Ref<boolean>
}
export function useVideoDeepLink(): VideoDeepLinkState {
const route = useRoute()
const router = useRouter()
// 从 URL 读取初始状态
const time = ref(Number(route.query.t) || 0)
const quality = ref((route.query.quality as string) || '1080p')
const subtitle = ref((route.query.subtitle as string) || '')
const autoplay = ref(route.query.autoplay === '1')
// 同步状态到 URL(防抖避免频繁更新)
let updateTimer: ReturnType<typeof setTimeout>
function syncToUrl() {
clearTimeout(updateTimer)
updateTimer = setTimeout(() => {
const query: Record<string, string> = {}
if (time.value > 0) query.t = String(Math.floor(time.value))
if (quality.value !== '1080p') query.quality = quality.value
if (subtitle.value) query.subtitle = subtitle.value
if (autoplay.value) query.autoplay = '1'
router.replace({ query })
}, 1000)
}
// 监听状态变化,自动同步到 URL
watch([time, quality, subtitle, autoplay], syncToUrl)
return { time, quality, subtitle, autoplay }
}<!-- app/pages/videos/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
const { time, quality, subtitle, autoplay } = useVideoDeepLink()
// 播放器时间更新时同步
function onTimeUpdate(currentTime: number) {
time.value = currentTime
}
// 分享当前播放位置
function shareCurrentPosition() {
const url = new URL(window.location.href)
url.searchParams.set('t', String(Math.floor(time.value)))
navigator.clipboard.writeText(url.toString())
}
</script>
<template>
<div v-if="video">
<VideoPlayer
:url="video.url"
:initial-time="time"
:quality="quality"
:subtitle="subtitle"
:autoplay="autoplay"
@time-update="onTimeUpdate"
@quality-change="(q) => quality = q"
@subtitle-change="(s) => subtitle = s"
/>
<div class="mt-4 flex items-center gap-4">
<h1 class="text-2xl font-bold flex-1">{{ video.title }}</h1>
<button @click="shareCurrentPosition" class="btn-secondary">
分享当前位置
</button>
</div>
</div>
</template>4.4 Hash 导航:滚动到评论区
<script setup lang="ts">
const route = useRoute()
// 页面加载时,如果 URL 有 #comments,滚动到评论区
onMounted(() => {
if (route.hash === '#comments') {
nextTick(() => {
document.getElementById('comments')?.scrollIntoView({ behavior: 'smooth' })
})
}
})
</script>
<template>
<div>
<VideoPlayer ... />
<VideoInfo ... />
<!-- 评论区锚点 -->
<section id="comments" class="mt-8">
<h2 class="text-xl font-bold">评论</h2>
<CommentList :video-id="video.id" />
</section>
</div>
</template>4.5 scrollBehavior 配合
// app/router.options.ts
import type { RouterConfig } from '@nuxt/schema'
export default <RouterConfig>{
scrollBehavior(to, from, savedPosition) {
// 浏览器后退/前进:恢复滚动位置
if (savedPosition) return savedPosition
// 带 hash:滚动到锚点
if (to.hash) {
return {
el: to.hash,
behavior: 'smooth',
top: 80, // 留出固定导航栏的高度
}
}
// 同一页面内的 query 变化:不滚动
if (to.path === from.path) return false
// 其他情况:滚动到顶部
return { top: 0, behavior: 'smooth' }
},
}本章小结
- useRoute:只读响应式对象,获取 path/params/query/hash/meta
- useRouter:路由实例,提供 push/replace/back/go 等导航方法
- navigateTo vs router.push:前者 SSR 通用,推荐优先使用
- 历史栈管理:push 添加记录、replace 替换记录,登录/表单提交后用 replace
- Query 参数:封装
useRouteQuery实现 URL↔状态的双向绑定 - 深链接:URL 精确描述页面状态(播放时间/画质/字幕),支持分享和恢复
- scrollBehavior:自定义滚动行为,配合 hash 锚点和历史记录恢复
至此,第二篇"路由系统精讲"全部 5 章已完成。你已经掌握了:
- 文件系统路由的映射规则与 NuxtPage 工作原理
- 动态路由、嵌套路由、路由分组的高级模式
- 路由中间件体系与权限拦截完整方案
- CSS 过渡动画与 View Transitions API 共享元素动画
- 程序化路由控制与深链接设计
下一篇将深入 组件系统精讲——组件自动导入、Props/Emits/Slots 高级用法、NuxtUI 集成。