加了 <base> 标签后,href="#section2" 会被解析为 /app/#section2 并触发重载;应使用 new URL("#section2", document.baseURI).href 或 preventDefault() + scrollIntoView() 避免问题。

加了 <base> 标签后,页面里所有没写全路径的 href、src、action 都会按它重算——包括你以为“只是跳锚点”的 href="#section2"。这不是 bug,是浏览器严格执行 URL 解析规范的结果。
base 标签会让 href="#xxx" 变成完整路径并触发重载
浏览器把 href="#section2" 当作相对 URL 处理(因为它不以 /、http:// 开头)。一旦页面有 <base href="/app/">,就会拼出 /app/#section2。这个地址和当前页路径不同,导致:
- History API 推入新记录,触发
popstate或整页重载(尤其在 SPA 中) - Network 面板能看到一次对
/app/的 200 响应(返回完整 HTML),这是重载证据 - 滚动不会发生,
scrollIntoView()不被调用,用户卡在顶部 - Vue Router / React Router 的 hash 模式可能完全不响应,因为路由监听的是
#section2,不是/app/#section2
如何安全地保留锚点语义又绕过 base 干扰
不能删 <base>(资源路径依赖它),也不能硬写 href="page.html#section2"(破坏 SPA 路由)。正确做法是让链接始终指向当前页面的完整 URL + hash:
- 服务端渲染时:用模板变量注入
href="{{ .CurrentPath }}#section2" - 客户端 React/Vue:写成
href={window.location.pathname + "#section2"} - 通用 JS 方案:用
new URL("#section2", document.baseURI).href—— 这个 API 会忽略<base>对 hash 的影响,返回当前页 URL 拼上 hash - 绝对不要手动拼
"#" + id后赋给href,它仍会被解析为相对路径
为什么 preventDefault() + 手动 scrollIntoView 是最可控的兜底方案
原生锚点行为在 <base> 存在时已不可信,显式控制更稳妥:
立即学习“前端免费学习笔记(深入)”;
- 给链接加
onclick="event.preventDefault(); document.getElementById('section2').scrollIntoView({ behavior: 'smooth' });" - 注意
scroll-margin-top必须加在目标元素(如<h2 id="section2">)上,不是父容器;且该 CSS 属性在 Firefox 旧版中不支持 - 首次加载带 hash(如
/app/page#faq)时,需在 DOM 渲染完成后检查location.hash并手动调用scrollIntoView(),否则路由初始化早于 DOM,找不到元素 - 若用 Vue Router 的
<router-link>或 React Router 的<Link>,它们内部已preventDefault(),不受<base>影响,但原生<a>不会
最容易被忽略的点:base 标签本身不改写 window.location 或 document.URL,但所有基于 href 的跳转行为都受其支配;哪怕你只用它管静态资源,只要页面里还留着一个 <a href="#top">,就可能在某个子路径部署时突然失效。



















