现象
命令面板(Ctrl/Cmd+K)、彩蛋终端的 open <section>、首页可交互终端——这些“输入即跳转”的交互,视觉上工作正常,但体验上有一处不对劲:点击跳转时页面整页重载。表现是 View Transitions 的淡入淡出动画没有出现,某些存在内存里的临时状态(比如本会话已展开的面板)被清空。
根因
这些跳转统一用了:
window.location.href = target;
在 Astro <ClientRouter>(或任何 History-API 驱动的 SPA 路由)里,路由拦截器监听的是 click 事件与 popstate,而不是 location.href 的赋值。直接改 location.href 属于“编程式整页导航”,浏览器会老老实实卸载当前文档、重新请求目标——路由的增量渲染层根本没机会介入。于是 SPA 退化成 MPA。
修复:合成锚点点击
让跳转“看起来像用户点了链接”,路由拦截器自然接管:
const navigateTo = (href) => {
if (!href) return;
const link = document.createElement('a');
link.href = href;
document.body.append(link);
link.click();
link.remove();
};
点击一个同域 <a> 会触发路由的 astro:link-click/拦截逻辑,走增量换页;而如果路由不可用(首屏未水合、浏览器不支持),这次点击自动降级为普通导航,不会比 location.href 更差。零额外依赖。
对需要绝对 URL 的场景(如接口返回的 statusUrl)同样适用,浏览器会解析 link.href。
何时仍该用 location.href
不是所有跳转都要 SPA 化。这些情况故意整页刷新更合理:
- 跳转到站外/不同子域(本就该脱离路由);
- 强制重载以拉取新构建产物(发布预览);
- 登录/登出后需要清空所有内存态。
区分标准是“目标是否在同一应用壳内”。站内常规导航交给路由,跨边界或需要重置的场景保留 location.href。
验证
一个便宜的回归断言:跳转前在页面 window.__spaMarker = 1,跳转后读回——若仍是 1 说明文档没被卸载,走了 SPA 路径;若变 undefined 说明整页刷新了。本项目命令面板与终端跳转的回归测试就用了这个标记法。