You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 23:50:35