路由设计 · 稳定入口与可变目标
「路由」远不止浏览器地址栏里那条 URL。它是一层可控的 indirection —— 对外暴露一个稳定入口 (一条 URL、一个服务端 route、一个 App 里看不见的 deep link、一条短链),入口背后的目标则随时可换。正因为多了这一层,才能统一改址、按渠道统计、灰度上线,而那些已经发出去的入口一个字都不用动。拆开这一层,里面是两半:一张规则表决定入口该去哪 (rule,见匹配引擎),再用某种机制把请求真正送过去 (dispatch —— reverse proxy / redirect / rewrite / 系统级路由)。下面分五组,从「为什么需要它」讲起,沿服务端 → 浏览器客户端 → 基础设施与前端深层逐层展开,每页都能改输入、即时看到路由跑出的结果。
路由是什么 · 为什么需要这一层
先把「稳定入口 ↔ 可变目标」的心智模型立起来,再看它最日常的一个实例 —— 短链为什么会存在。
为什么要有路由这一层
与其让每个使用方硬编码最终地址,不如对外只给一个稳定入口。这一层拆成 rule 与 dispatch 两半,换来统一改址、集中统计与安全下线。
短链接为什么会存在
长 URL 机器能用、人却用不了:没法念、塞不进短信、印成二维码后码点过密。短链解决的首先是长度问题,附带才是 indirection 的改址与统计。
服务端路由 · 一张表 + 一次 dispatch
最经典的路由都在服务端:一张表把 path 匹配到 handler,命中后用 3xx 或 rewrite 把请求送达。先拆匹配引擎与重定向语义两块内核,再落到三个真实场景 —— 分享统一入口、App deep link、远程配置入口。
路由匹配引擎
拿一张路由表自上而下匹配一条 path。三种 segment、参数提取,以及三条让「最该命中的那条」命中的路:顺序、specificity 打分、radix tree。
HTTP 重定向的五个状态码
服务端回一个 3xx 加 Location 就把客户端指去别处。五个码只差两个正交维度:永久还是临时(能否被缓存)、方法保不保持(POST 跟过去还是被改成 GET)。
分享地址的统一入口
各渠道分享链接全部先打到 /s/:code,命中后统计、查表、302 跳走。附 open redirect 的成因与 allowlist 防御,以及 SPA 下服务端只认三条路由的配置。
App 里看不见的路由
点一条链接直接跳进 App 的某个页面,要面对「装了进 App、没装退回网页」这道分叉。自定义 scheme、Universal Links 与 App Links、以及 deferred deep link 三种做法。
远程配置入口 · 让 App 不发版也能改地址
二维码、已装的 App、烧进设备的固件发出去就改不动。里面不写死真实地址、只写死一个稳定入口,真实地址放服务端配置,改地址就与发版彻底分家。
客户端 / 浏览器路由 · 同一套模型搬进浏览器
把这一层搬进浏览器 —— 单页应用 (SPA) 不刷新整页、自己维护一份「当前 location」与 history 栈,共用同一套「稳定入口 ↔ 可变目标」模型,只是状态从服务端挪到客户端。末两页是浏览器为路由新做的原生接口。阅读前建议先了解 URL 的组成 (scheme/host/path/query/fragment),可参考 URL Anatomy 系列。
客户端路由 · push / replace 与 history 栈
SPA 自己维护一份「当前 location」并与浏览器 history 栈同步。把栈画成「一列条目加一个指针」,看清 push 与 replace 的差别,以及 pushState 不发 popstate 这个易错点。
history 模式与 hash 模式
分水岭是一个底层事实:URL 里 # 后面那段不发给服务端。刷新一条深层路由时,history 模式没配 fallback 就 404,hash 模式永远只请求根路径。
软导航 · 守卫、滚动恢复与异步
导航搬进客户端后从一次性动作变成可中途插手的过程。守卫在跳转前拦一道、滚动恢复接管浏览器失灵的天生行为、异步导航挂起等数据到位再提交。
Navigation API · 一个事件接住所有导航
History API 搭 router 留下三处不便:pushState 不发 popstate、<a> 要手动拦、读不到历史条目。Navigation API 把所有导航汇成一个 navigate 事件。
URLPattern · 浏览器内建的 URL matcher
手工实现的 path 匹配引擎被浏览器标准化成了 URLPattern:匹配范围从 pathname 一段扩到整条 URL 的八个组件,语法扩到正则约束、可选分组与可重复段。
基础设施层的 dispatch · 那台 matcher 搬到最前层
把匹配引擎搬到服务端最前面那一层,决定一条请求转发给谁 (网关按 = > 最长 prefix > regex 择优)、按什么比例 (加权流量切分,canary / 灰度 / 蓝绿的底座)、按哪个维度 (多租户用 host 而非 path)。
反向代理与 API 网关
把 path 到 handler 的引擎搬到服务端最前一层。nginx 的择优规则不是「写在前面的先命中」,而是 =、^~、正则、普通最长前缀四档固定优先级。
加权流量切分与金丝雀发布
把「目标」从确定值变成概率分布:同一入口背后多个版本同时在线,路由层按权重分流。这是灰度、蓝绿与 A/B 的共同底座,附会话黏滞与请求头定向两个机制。
多租户 · 用 host 路由到租户
路由维度从 path 换成 host。通配子域加通配证书撑起零运维开通,代价是要处理保留子域、站点根域与自定义域三类例外,且通配证书只覆盖一层。
前端更深的路由机制 · 超出「平表匹配」的进阶
回到前端,几个超出「一条 path 命中一条路由」的机制:一条 path 命中一整条嵌套链 (layout 层层复用)、一条 URL 同时填多个槽 (并行 / 拦截路由)、locale 写在前缀或域名的国际化路由、把无数等价写法收敛到唯一地址的 URL 规范化。
嵌套路由与 layout
平表里一条 path 命中一条路由;真实前端的路由是一棵树,一条 path 命中从根到叶的一整条链。切换兄弟页面时共享的外层 layout 原地不动,这是平表给不了的性质。
并行路由与拦截路由
一个 layout 有多个具名 slot,一条 URL 同时把它们填好。配上拦截路由,同一个地址在软导航下浮成模态、在硬导航下渲染整页。
移动端页面栈与模态栈
「从 B 退回 A 时 A 原封不动」的关键只有一条:A 从来没被销毁过。声明式引擎把 URL 翻译成期望栈,与实际挂载做 diff,公共前缀保活、多出来的销毁、缺的补上。
移动端页面栈 · 整页版
页面栈引擎的整页版:push 保活、pop 即销毁、设置模态从底部滑入、左缘侧滑返回、直达深层地址时合成背景。与讲解版同一套 reconcile。
页面栈的 vue-router 实现
同一套页面栈 scheme 落到 vue-router 的现成原语上:meta.layer 定方向、<transition> 分进退、<keep-alive :include> 白名单即「还活着的栈」。
页面栈 · vue-router 整页版
同一套页面栈 scheme 收敛成整视口的一台 App,四条 vue-router 原语各管一段:meta.layer、<transition>、<keep-alive :include> 与 beforeEach 守卫。
国际化路由
locale 写进 URL 才可分享、对 SEO 友好,路径前缀与域名是两种写法。URL 不带 locale 时读 Accept-Language 协商:一串带 q 权重的偏好加 fallback 链。
URL 规范化与 canonical 地址
同一篇文章可被无数写法不同却等价的 URL 指向。规范化用一条幂等的变换流水线把它们收敛到唯一 canonical 地址,擦完不一样就回 301。
🔗 相关链接
- MDN · HTTP Redirections developer.mozilla.org 对应「重定向」页:301/302/303/307/308 各自语义、永久 vs 临时、方法保持的逐项说明与典型用途。
- RFC 7231 §6.4 · Redirection 3xx IETF 权威定义:为什么 307/308 是「方法明确版」的 302/301,以及 303 强制改 GET 的规范出处。
-
React Router · Routing
reactrouter.com
对应「匹配引擎」页:前端路由的 dynamic segment
:id、splat*、嵌套与最具体者优先的真实实现。 -
Apple · Universal Links
developer.apple.com
对应「deep link」页:iOS 用
apple-app-site-association把 https 链接路由进 App 的官方机制 (Android 侧对应 App Links +assetlinks.json)。 - OWASP · Unvalidated Redirects and Forwards owasp.org 对应「分享入口」页的安全小节:open redirect 的危害与 allowlist 防御 —— 跳转目标为什么必须校验。
- MDN · History API developer.mozilla.org 对应「客户端路由」「history 模式」两页:pushState / replaceState / go / back / forward 与 popstate 事件的底层动作。
- MDN · Navigation API developer.mozilla.org 对应「Navigation API」页:navigate 事件、intercept、entries()、currententrychange —— 为客户端路由重做的新一代接口。
- MDN · URLPattern API developer.mozilla.org 对应「URLPattern」页:构造器、test() / exec()、整条 URL 各组件的 pattern 语法与命名组提取 —— 浏览器内建的 URL matcher。
-
nginx · location 指令
nginx.org
对应「反向代理 / API 网关」页:
=/^~/~/ 普通前缀四种 location 的匹配优先级与求值顺序 —— 那个「正则反超前缀」的陷阱出处。 - Istio · Traffic Management istio.io 对应「加权流量切分」页:service mesh 怎样用 VirtualService / DestinationRule 按 weight 切 subset、按 header 定向 —— canary / 灰度的生产实现。
-
Next.js · Parallel & Intercepting Routes
nextjs.org
对应「并行 / 拦截路由」页:
@slot具名槽、(.)/(..)/(...)拦截约定、default.js兜底的官方模型。