链接即契约:URL 的语义价值

URL 是 Web 的地址系统,也是用户和搜索引擎理解页面内容的入口。一个好的 URL 本身就是说明文档:看到 /products/phones/2026 就知道是 2026 年手机产品列表,看到 ?page=2 就知道是第二页。URL 一旦发布就被收藏、被分享、被收录,改动会造成死链与权重流失,所以 URL 是长期契约,设计时要当 API 来对待。

前端路由让单页应用也能拥有语义化 URL:不刷新页面就能切换视图,同时地址栏、前进后退、分享链接全部保持正常。本文讲清 URL 设计与前端路由的完整链路。

本文核心知识点
  • 语义URL 反映内容层级与含义。
  • history无刷新切换并同步地址栏。
  • 参数路径与查询参数承载状态。
  • 守卫按登录与权限拦截路由。
URL 设计与前端路由配图:蓝紫渐变背景上方一条纵向道路分叉出多条彩色曲线,每条曲线连接带圆形连接点的功能模块与下方悬挂的小面板,扁平矢量插画
一条主路径分叉通向多个功能节点,正是路由分发的意象。

语义化 URL 设计规范

语义化 URL 有几条硬规范:用小写字母加连字符,单词间用连字符不用下划线;路径反映内容层级,从泛到专;查询参数用于过滤与状态,不用来定位内容;避免无意义编号与动词碎片,能读出来的 URL 才是好 URL。旧链接迁移要做 301 重定向,把权重与用户带到新地址。

# 好:能读出含义
/products/phones/2026
/articles/web/url-routing

# 差:含糊不清
/p?id=1234
/do?action=show&kind=2

URL 长度要克制,深链层级超过四层就要考虑扁平化;中英文混杂要避免;时间与版本这类易变信息放在查询参数里,而不是塞进路径,减少 URL 变更频率。

history 路由与 hash 路由

前端路由有两种模式。hash 模式用 # 号后面的部分做路由,刷新不丢状态、无需服务端配合,但 URL 带 # 不够美观,搜索引擎收录也弱。history 模式用 History API 的 pushState 与 popstate 实现,URL 干净漂亮,但刷新时服务端要配置把未知路径都回退到入口页,否则直接 404。

// history 模式:无刷新切换路由
history.pushState({ page: "detail" }, "", "/products/1");
window.addEventListener("popstate", (e) => {
  renderRoute(location.pathname);
});

选型建议:对外的产品站与内容站用 history 模式加服务端回退,SEO 友好;内嵌工具、原型与无法配置服务端的场景用 hash 模式。现代框架路由库都同时支持两种,切换成本不高。

路由状态与参数管理

路由不只是地址,还是应用状态的一部分。路径参数承载主标识,比如 /products/1 里的 1;查询参数承载次要状态,比如筛选条件、页码、排序;这两个来源要跟组件内部状态明确分工,让"刷新后状态还在"成为默认行为。深层状态如果塞进 URL,地址会爆炸,所以要分级:核心状态进 URL,瞬时状态留在内存。

// 读取并同步查询参数
const params = new URLSearchParams(location.search);
const page = Number(params.get("page")) || 1;
// 更新时不丢失其他参数
params.set("page", page + 1);
history.replaceState(null, "", "?" + params.toString());

参数解析要做容错:非法页码回退默认值,缺失参数给合理初始态。pushState 与 replaceState 的区别要分清:push 增加历史记录、可后退,replace 替换当前记录、适合页码这类不希望产生历史堆栈的更新。

路由守卫与懒加载

很多页面有访问条件:未登录跳登录页,无权限提示 403。路由守卫把这类逻辑集中处理,比每个页面自己判断更可控。守卫的典型流程:解析目标路由与当前用户态,判断放行、重定向还是阻断,并记录来源路径,登录成功后回跳。

// 守卫示意:未登录重定向
function guard(to) {
  if (to.meta.requiresAuth && !isLoggedIn()) {
    return { redirect: "/login", query: { from: to.path } };
  }
  return true;
}

懒加载与路由配合是性能利器:按路由切分代码,访问到哪个页面才加载哪块 JS。首屏只加载首屏需要的模块,其余页面代码按需拉取,配合预取能明显加快首屏与后续导航。