写了八年 SPA 路由,今天才发现 History API 那套拼装逻辑全白搭了—— Navigation API 把这件事彻底变了
写 SPA 绕不开路由,而路由写了八年,每个人都是「拼」出来的。
一个典型的前端路由要同时维护这几件事:给所有 <a> 标签绑全局点击拦截、调用 preventDefault()、手动 pushState、更新 DOM、单独再监听 popstate 处理前进后退。如果漏了任意一环,用户就可能点进错误视图而没有任何报错。这种脆弱的架构不是开发者的错——History API 设计出来的时候压根不是给 SPA 用的。
Navigation API 从根本上把这件事收口了。
一个 navigate 事件监听器统一接收所有类型的导航:链接点击、表单提交、前进后退、编程调用。event.intercept() 替开发者完成 URL 更新、历史堆栈管理、焦点恢复这些重复劳动,而且还内置 navigatesuccess 和 navigateerror 事件做集中错误处理。更关键的是:现在可以通过 navigation.entries() 查看完整历史堆栈了,entry.getState() 可以读取任意历史节点上挂的 state——这两件事在 History API 时代是完全做不到的。
用起来很简单:
navigation.addEventListener(navigate, (event) => {
// 跨域和下载请求不过滤
if (!event.canIntercept) return;
if (event.hashChange || event.downloadRequest) return;
// 接管导航
event.intercept({
async handler() {
// 异步获取内容
const response = await fetch(event.destination.url);
const html = await response.text();
// DOM 更新在 View Transition 回调里还能触发原生过渡动画
document.startViewTransition(() => {
document.querySelector(#app).innerHTML = html;
});
}
});
});
这二十行代码,把原来分散在四五个地方的事件监听和状态管理全部收进来了。event.intercept() 自动处理 URL 更新、焦点恢复、滚动行为;开发者只管 DOM 更新这一件事。
表单提交也原生支持了。 标准 <form method="POST"> 提交可以在同一个监听器里拦截,不需要额外写 onsubmit:
if (event.formData && event.canIntercept) {
event.intercept({ async handler() {
const data = event.formData;
postFormData(data);
renderSuccessMessage(data.get(username));
}});
}
滚动时机终于可以控制了。 SPA 里最烦的场景之一:用户点后退返回上一页,浏览器在内容还没加载完的时候就恢复了滚动位置,结果滚到了一个空区域。Navigation API 的解法是 scroll: manual,等数据渲染完了再手动调用 event.scroll():
event.intercept({
scroll: manual,
async handler() {
const data = await fetchListData();
renderItems(data);
// DOM 有高度了再恢复滚动位置
event.scroll();
}
});
完整历史堆栈可以读取了。 navigation.entries() 返回同源所有历史节点,navigation.traverseTo(entryKey) 可以直接跳到任意历史位置。这意味着自定义返回按钮、基于历史状态的导航这些需求,第一次有了正经平台支持。
React Router 和 TanStack Router 都已经公开讨论过将 Navigation API 作为底层支撑的计划,不过目前还没有正式集成。这个 API 定位在框架路由之下——给框架提供平台原语,而非直接竞争。
迁移路径很清晰。由于是渐进增强,保守策略是:
// 能力检测
if (window.navigation) {
// 用 Navigation API 接管
navigation.addEventListener(navigate, handleNavigate);
} else {
// 降级到原有 History API 逻辑
window.addEventListener(popstate, handlePopState);
}
实操中建议从 navigation.navigate(/path, { history: replace }) 替代 history.pushState() 开始,逐步将 popstate 监听器迁移到 navigate 事件。
支持情况:Chrome、Edge、Firefox 147、Safari 26.2 全部上线,Baseline 2026,属于 2026 年可以直接上生产的技术。
Navigation API 的意义不只是省几行代码。它把 SPA 路由从「拼装补丁」变成了「平台级原语」——所有导航类型由同一个事件处理,所有状态由同一个 API 管理,所有边界情况由浏览器统一兜底。对于前端开发者来说,路由不再是需要从零搭的脚手架,而是有史以来第一次有了正经平台支撑的基础设施。
评论区
登录后可评论。