浏览器导航与前端路由
从浏览器导航、会话历史、History API 到前端路由器,梳理 URL 变化如何驱动网络请求、应用状态和视图渲染。
[!info] related notes
- 所属 MOC: 前端基础 MOC, javascript-in-browser-moc
- 上位主题: javascript-in-browser
- 相关概念: 路由, History 对象, location-object, URL, SPA vs MPA, 页面运行时状态
- 框架落地: Vue Router
- 相关机制: BFCache 页面级缓存, 浏览器输入 URL 后发生了什么
浏览器导航与前端路由
范围
这篇笔记关心的不是“某个路由库 API 怎么用”,而是更底层的问题:
- URL 变化时,浏览器会做什么
- 哪些变化会触发网络请求,哪些不会
- 前端路由器到底接管了浏览器哪一部分工作
为什么要放在一起理解
“路由”这个词很容易把三层东西混在一起:
- 浏览器导航机制
- 前端应用里的客户端路由
- 服务端对 URL 的资源分发
如果不先分层,后面讲 hashchange、pushState()、router.push()、服务端 fallback 时就很容易串线。
先抓住总问题
当 URL 变化时,系统要回答四件事:
- 要不要请求服务器
- 历史记录怎么变
- 当前应用状态怎么同步
- 最终该渲染什么内容
路由本质上就是这四件事的协作机制。
浏览器导航在做什么
浏览器层的“导航”不只包括点击链接,还包括:
- 地址栏输入 URL
- 点击
<a href> - 表单提交
location.assign()/location.replace()- 前进 / 后退
- 刷新
从结果看,可以粗分成两类:
1. 会触发新文档加载的导航
典型表现:
- 浏览器发起新的 HTTP 请求
- 新 HTML 返回
- 旧文档被卸载
- 新页面重新解析和执行
这类更接近传统 MPA 导航。
2. 不触发整页刷新的同文档导航
典型表现:
- 地址变了
- 历史记录可能变化
- 但当前文档并没有被整体替换
常见来源:
- hash 变化
history.pushState()history.replaceState()
这类能力给 SPA 留出了接管空间。
会话历史是底层骨架
浏览器会维护当前 tab 的 session history。
它决定了:
- 前进 / 后退能回到哪里
- 当前历史条目关联什么 URL
- 某些条目是否附带
history.state
history 对象只是这套机制暴露给脚本的接口。见 History 对象。
前端路由器到底接管了什么
SPA 不是浏览器天然就会的能力,而是前端代码主动接管了默认导航流程。
典型过程是:
- 用户点击链接
- 前端阻止浏览器默认整页跳转
- 前端调用
pushState()或改hash - 路由器解析 URL
- 路由器匹配目标视图
- 执行守卫、数据获取、懒加载
- 更新当前 UI
所以前端路由不是“浏览器帮你切页面”,而是“前端借用浏览器的 URL 和历史能力,在同一份文档里组织页面系统”。
Hash 路由和 History 路由的边界
Hash 路由
- URL 中
#后面的 fragment 不会发给服务器 - 适合静态托管或部署回退不方便的场景
- 地址可见,但路径语义较弱
History 路由
- 路径更自然,例如
/users/123 - 基于
pushState()/replaceState()/popstate - 需要服务端把未知前端路径回退到
index.html
两者都是客户端路由方案,区别不在“谁能切页面”,而在“URL 形态”和“是否需要服务端协作”。
为什么服务端 fallback 必不可少
在 History 路由下,如果用户直接访问 /users/123,浏览器会真的去请求这个路径。
如果服务端只认真实文件或真实页面资源,而没有把未知前端路径回退给 SPA 入口页,结果通常就是 404。
所以服务端 fallback 的本质是:
服务器把“这个路径该展示哪个前端页面”的决定权交回给前端路由器。
URL 不只是地址,也是可序列化状态
路由系统的价值不只是让页面“能切换”,还在于让一部分应用状态变得可分享、可恢复、可追踪。
适合编码进 URL 的常见状态:
idpagesortkeywordtab
不适合直接放进 URL 的常见状态:
- 临时弹窗开关
- hover 状态
- 大体积对象
- 敏感 token
所以从系统视角看,路由本质上也是一种状态分层机制。
路由和页面运行时状态的关系
前端应用里有些状态应该进入 URL,有些只应该留在内存里。
- URL 状态负责分享、恢复和前进后退
- 页面运行时状态负责当前交互现场
见 页面运行时状态。
如果一份状态只存在运行时内存里,那么:
- SPA 内部切换时也许还能保住
- 整页刷新或新文档导航时通常会丢
前进后退为什么经常比想象中复杂
因为浏览器恢复的不只是 URL,还可能涉及:
history.state- 滚动位置
- 表单值
- BFCache 快照恢复
所以“回到上一个页面”并不总等于“重新跑一遍初始化逻辑”。
和框架路由的关系
Vue Router、React Router、Next.js App Router 这些框架路由,本质上都是在浏览器原生导航机制之上,再补上:
- 路由匹配
- 参数提取
- 守卫
- 懒加载
- 数据获取
- 布局嵌套
浏览器负责 URL 与历史;框架负责把这些底层能力组织成应用级页面系统。
最短记忆方式
浏览器导航负责处理 URL、历史和文档切换;前端路由负责在这些能力之上,把 URL 进一步映射成应用状态和界面结构。