Angular SSR应用刷新弹出‘Invalid token’错误及mat-dialog异常求助
Angular SSR 应用刷新问题排查与修复
问题现象
- 用户刷新应用时,自动弹出mat-dialog提示「Invalid token」,但浏览器DevTools网络面板无任何错误请求记录
- SSR渲染生成的HTML未包含自定义mat弹窗模块的DOM结构,导致弹窗无法正常交互(关闭按钮等操作无响应)
相关代码
app.module.ts
@NgModule({ declarations: [ AppComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), BrowserModule, AppRoutingModule, BrowserAnimationsModule, MatSnackBarModule, CookieModule.withOptions(), BrowserTransferStateModule, StoreModule.forRoot(rootReducer, { metaReducers }), EffectsModule.forRoot(rootEffects), StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }), NgxMaskModule.forRoot(), HttpClientModule, ErrorsPopupModule ], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, ], bootstrap: [AppComponent], }) export class AppModule { }
app.server.module.ts
@NgModule({ imports: [ AppModule, ServerModule, CookieBackendModule.withOptions(), BrowserModule, BrowserModule.withServerTransition({ appId: 'serverApp' }), ServerTransferStateModule, ], bootstrap: [AppComponent], }) export class AppServerModule { }
auth.guard.ts
@Injectable({ providedIn: 'root', }) export class AuthGuard implements CanActivate { constructor( private _cookieService: CookieService, private _router: Router, ) { } canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { if (this._cookieService.get('token')) { console.log('authenticated!..'); return true; } else { console.log('Not authenticated!'); return this._router.createUrlTree(['auth']); } } }
auth.interceptor.ts
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private _cookieService: CookieService, private _errorsService: ErrorsService,) { } intercept( req: HttpRequest<any>, next: HttpHandler, ): Observable<HttpEvent<any>> { const token = this._cookieService.get('token'); if ( !req.url.includes('login') && !req.url.includes('restore_password') && !req.url.includes('verify') && !req.url.includes('register') && !req.url.includes('spheres') ) { if (token) return next.handle(req.clone({ setHeaders: { 'Authorization': token, }, })).pipe( catchError((err: HttpErrorResponse) => { console.log('intercept:', err); this._errorsService.openPopup(err) return [] })); } return next.handle(req) } }
问题分析与修复
1. 刷新弹出「Invalid token」弹窗的修复
原因
SSR渲染阶段,服务器端无法直接读取浏览器Cookie(除非服务器配置了Cookie传递逻辑),导致拦截器拿到的token为空或无效值。页面刷新时,服务器端发起的请求因无有效token返回401错误,触发拦截器的错误处理逻辑弹出弹窗,但服务器端请求不会显示在浏览器网络面板中,所以看不到错误记录。
修复步骤
- 给拦截器注入平台标识,区分服务器端与浏览器端逻辑,仅在浏览器端弹出错误弹窗:
import { isPlatformBrowser, isPlatformServer } from '@angular/common'; import { Inject, PLATFORM_ID } from '@angular/core'; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor( private _cookieService: CookieService, private _errorsService: ErrorsService, @Inject(PLATFORM_ID) private platformId: Object ) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = this._cookieService.get('token'); const skipUrls = ['login', 'restore_password', 'verify', 'register', 'spheres']; if (!skipUrls.some(url => req.url.includes(url))) { if (token) { return next.handle(req.clone({ setHeaders: { 'Authorization': token } })).pipe( catchError((err: HttpErrorResponse) => { // 仅在浏览器端执行弹窗逻辑 if (isPlatformBrowser(this.platformId)) { this._errorsService.openPopup(err); } return []; }) ); } } return next.handle(req); } }
- 检查后端服务器配置(如Express),确保请求时将Cookie传递给Angular SSR应用。
2. SSR弹窗模块缺失、交互失效的修复
原因
ErrorsPopupModule未适配SSR环境,或者弹窗组件直接调用了浏览器专属API(如window、document),导致SSR渲染时跳过该组件,最终HTML中没有弹窗DOM结构,浏览器端hydrate时无法绑定事件。
修复步骤
- 确保
ErrorsPopupModule在服务器端模块中正常导入(已通过AppModule间接导入,需确认模块本身支持SSR) - 弹窗组件中增加平台判断,避免服务器端执行浏览器专属逻辑:
import { isPlatformBrowser } from '@angular/common'; import { Inject, PLATFORM_ID } from '@angular/core'; export class ErrorsPopupComponent { constructor(@Inject(PLATFORM_ID) private platformId: Object) {} ngOnInit() { if (isPlatformBrowser(this.platformId)) { // 仅在浏览器端初始化弹窗相关逻辑 } } closePopup() { if (isPlatformBrowser(this.platformId)) { // 仅在浏览器端执行关闭操作 } } }
- 如果需要从服务器端传递弹窗状态到浏览器,使用
TransferState存储状态,浏览器端hydrate时读取状态决定是否显示弹窗。
额外优化:AuthGuard适配SSR
在路由守卫中区分平台,避免服务器端因无法获取Cookie错误跳转:
import { isPlatformServer } from '@angular/common'; import { Inject, PLATFORM_ID } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor( private _cookieService: CookieService, private _router: Router, @Inject(PLATFORM_ID) private platformId: Object ) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | boolean | UrlTree { // 服务器端直接放行,让浏览器端再做权限校验 if (isPlatformServer(this.platformId)) { return true; } return this._cookieService.get('token') ? true : this._router.createUrlTree(['auth']); } }
内容的提问来源于stack exchange,提问作者pasha vibe
相关产品推荐
相关产品推荐

