前端路由本质是url与视图的映射机制,依赖javascript监听hashchange或popstate事件,结合history api动态更新dom,纯html无法实现;生产环境应优先选用history模式而非hash模式。

纯 HTML 本身无法实现前端路由——它没有运行时、不监听 URL 变化、也不执行匹配逻辑。所谓“基于 HTML 的轻量级前端路由组件”,本质是用少量 JavaScript 封装 History API 或 hashchange,配合语义化 HTML 结构(如 <router-view></router-view>、<route-link></route-link>)提供声明式体验,而非依赖框架。
用原生 History API 实现最小可行路由
核心是拦截点击、改写 URL、不刷新页面,并触发视图更新。不需要 Vue Router 那样的完整守卫体系,只需三步:
- 监听
popstate事件处理浏览器前进/后退 - 劫持
<a></a>点击,调用history.pushState()替代默认跳转 - 根据
location.pathname匹配预设规则,替换<router-view></router-view>内容
注意:必须服务端 fallback(如 Nginx 配置 try_files $uri $uri/ /index.html;),否则直接访问 /user/123 会 404。
为什么别用 hashchange 做生产路由
虽然 location.hash = '/foo' 和 window.addEventListener('hashchange') 零配置、不用服务端支持,但它有硬伤:
- URL 中的
#对 SEO 不友好,搜索引擎可能忽略 hash 后内容 - 部分分析工具(如 GA4)默认不采集 hash 变化,需额外配置
-
pushState支持真实路径、可被后端识别、兼容微前端 base path 对齐,而 hash 路由在子应用中无法与主应用base协同
除非你只做本地 demo 或离线 PWA,否则应优先选 history 模式。
<router-link></router-link> 和 <router-view></router-view> 必须是自定义元素
它们不是标准 HTML 标签,但可以安全使用:CustomElementRegistry.define() 注册后,浏览器会将其视为合法元素,且不影响解析与渲染。
-
<router-link to="/about">关于</router-link>渲染为带href的<a></a>,点击时 preventDefault 并 pushState -
<router-view></router-view>是一个空容器,每次路由变更后用 innerHTML 或 replaceChildren 更新其内容 - 不要用
<div id="router-view"> 这类泛化 ID——它破坏封装性,且无法通过 CSS 选择器精准作用于路由上下文 <p>这类自定义元素必须带 <code>display: block或display: contents(后者更轻量),否则在 Flex/Grid 布局中可能意外换行或占位。状态同步和跨组件通信容易被忽略
纯 HTML + JS 路由没有
useLocation()或$route响应式对象,所有组件要读取当前路径,必须手动订阅:- 用
new EventTarget()创建全局路由事件总线,每次pushState后dispatchEvent(new CustomEvent('routechange', { detail: { pathname } })) - 组件内用
eventTarget.addEventListener('routechange', handler)接收,记得在卸载时removeEventListener - 避免直接读
location.pathname—— 它在pushState后立即更新,但视图更新是异步的,可能导致竞态(比如刚跳转就立刻请求数据,却拿到旧路径)
这个细节在简单 demo 里不显眼,但在含表单、动画、权限校验的页面里,会直接导致白屏、重复提交或跳转错乱。
- 用











