Angular 2/4/5/6 PathLocationStrategy动态base href子路由失效问题
咱们先拆解一下你遇到的问题根源:当动态设置<base href>后,浏览器会用这个base来解析页面里的相对路径资源,但刷新子路由或直接硬链接访问时,服务器会先收到请求——如果你的服务器配置是把所有非静态资源请求重定向到index.html,那当浏览器请求/base/parent/runtime.js这类不存在的路径时,服务器会错误地返回index.html,导致js文件加载失败,页面直接崩掉。
下面给你几个实用的解决方案,你可以根据自己的部署场景选择:
方案一:给静态资源加绝对路径(适配动态base)
直接把index.html里的所有script、link标签的资源路径改成和动态base匹配的绝对路径。你可以在页面加载时用脚本动态计算并修正路径:
<script> // 先算出你的动态base href,这里假设它是URL路径的第一个非空段,比如 /base/ const baseSegment = window.location.pathname.split('/').filter(s => s)[0]; const baseHref = `/${baseSegment}/`; // 插入base标签 const baseEl = document.createElement('base'); baseEl.href = baseHref; document.head.prepend(baseEl); // 修正所有静态资源的路径 ['script[src]', 'link[href]'].forEach(selector => { document.querySelectorAll(selector).forEach(el => { const src = el.getAttribute('src') || el.getAttribute('href'); if (src && !src.startsWith('http')) { el.setAttribute(el.tagName === 'SCRIPT' ? 'src' : 'href', baseHref + src); } }); }); </script> <!-- 原来的资源引用保持相对路径即可,上面的脚本会自动修正 --> <script src="runtime.js"></script> <script src="polyfills.js"></script>
这样不管你在哪个子路由下,浏览器都会请求/base/runtime.js这种正确路径的资源,不会跑偏到子路由路径里。
方案二:改用哈希路由(最简单的无痛方案)
如果你的产品能接受路由里带#,那这个方案绝对是最省心的。哈希路由下,浏览器只会把#前面的路径发送给服务器,不管你刷新/base/#/parent还是直接访问这个链接,服务器收到的请求都是/base/,直接返回index.html,Angular加载后再解析#后面的子路由,完全不会出现资源路径错误的问题。
启用哈希路由只需要修改AppModule的配置:
import { LocationStrategy, HashLocationStrategy } from '@angular/common'; @NgModule({ declarations: [/* 你的组件 */], imports: [/* 你的模块 */], providers: [ // 替换成哈希路由策略 { provide: LocationStrategy, useClass: HashLocationStrategy } ], bootstrap: [AppComponent] }) export class AppModule { }
方案三:调整服务器配置,区分静态资源和路由请求
如果不想用哈希路由,那可以修改服务器配置,让它只把非静态资源的请求重定向到index.html,静态资源(比如.js、.css、图片)直接返回对应文件。
举两个常用服务器的配置例子:
Nginx配置
location /base/ { # 先尝试访问请求的文件/目录,存在就直接返回;不存在才重定向到index.html try_files $uri $uri/ /base/index.html; }
Apache(.htaccess配置)
RewriteEngine On # 如果请求的不是文件也不是目录,才重定向到index.html RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^base/(.*)$ /base/index.html [L]
这样配置后,当浏览器请求/base/runtime.js时,服务器会找到对应的文件直接返回;请求/base/parent这种路由路径时,才会返回index.html,完美解决资源加载错误的问题。
方案四:用Angular初始化器动态修正路径
和方案一类似,但更贴合Angular的生命周期,用APP_INITIALIZER在应用启动前完成配置:
- 创建初始化服务:
import { Injectable } from '@angular/core'; import { APP_INITIALIZER } from '@angular/core'; @Injectable() export class BaseHrefInitializer { initialize(): Promise<void> { return new Promise(resolve => { const baseSegment = window.location.pathname.split('/').filter(s => s)[0]; const baseHref = `/${baseSegment}/`; // 设置base href const baseEl = document.createElement('base'); baseEl.href = baseHref; document.head.prepend(baseEl); // 修正资源路径 document.querySelectorAll('script[src]').forEach(script => { const src = script.getAttribute('src'); if (src && !src.startsWith('http')) { script.setAttribute('src', baseHref + src); } }); resolve(); }); } } // 提供初始化器 export const BaseHrefInitializerProvider = { provide: APP_INITIALIZER, useFactory: (initializer: BaseHrefInitializer) => () => initializer.initialize(), deps: [BaseHrefInitializer], multi: true };
- 在AppModule里注册服务:
@NgModule({ // ...其他配置 providers: [ BaseHrefInitializer, BaseHrefInitializerProvider ] }) export class AppModule { }
总结
- 如果想最快解决问题,选方案二(哈希路由);
- 如果需要美观的URL且有权限修改服务器配置,选方案三(服务器配置);
- 如果服务器配置无法修改,选方案一或四。
内容的提问来源于stack exchange,提问作者E. Gregious

