Angular 17 SSR模式下页面刷新/路由跳转时登录页闪烁问题求助
问题描述
成功登录后用户跳转至首页,但页面刷新或路由跳转至其他页面时,登录页会短暂闪烁。推测原因是页面先执行服务端渲染(SSR),再进行客户端渲染(CSR):页面刷新时,服务端渲染阶段因无法读取客户端sessionStorage中的token,判定认证状态为false,从而渲染登录页发送至客户端;但客户端初始化后能读取到token,认证状态变为true,随即切换到首页或目标页面,期间路由未发生变化。关闭SSR后闪烁问题消失,现寻求无需关闭SSR即可解决该问题的方案。
相关代码
authGuard.ts
export const AuthGuard: CanActivateFn | CanActivateChildFn = (route, state) => { const router: Router = inject(Router); return inject(AuthService).checkAuthentication() .pipe( switchMap((isAuthenticated)=>{ if (!isAuthenticated) { return router.createUrlTree(['auth/sign-in'], {queryParams: {param: state.url}}) } return of(true); }) ); }
app.route.ts
export const routes : Routes = [ { path: '', component: LayoutComponent, canActivate: [AuthGuard], canActivateChild: [AuthGuard], children: [ { path: 'home', component: DashboardComponent }, { path: 'settings', loadChildren: () => import('./modules/settings/settings.routes') }, { path: 'users', loadChildren: () => import('./modules/users/users.routes')}, ] }, { path: 'auth', loadChildren: ()=> import('./login/login.routes') }, { path: '', pathMatch: 'full', redirectTo: 'home' }, { path: '**', pathMatch: 'full', redirectTo: 'home' }, ];
auth.service.ts
@Injectable({ providedIn: 'root' }) export class AuthService { private _storeService = inject(StoreService); signIn(credentials: { username: string; password: string }): Observable<any> { return this._httpClient.post(AUTH_API + 'User/login', credentials).pipe( switchMap((response: any) => { this._storeService.saveAccessToken(response.token); // save token on sessionStorage this._storeService.saveActiveUser(response.user); return of(response); }), ); } get accessToken(): string { return this._storeService.getAccessToken() ?? ''; } checkAuthentication(): Observable<boolean> { if ( !this.accessToken || AuthUtils.isTokenExpired(this.accessToken)) { return of(false); } else { return of(true) } } }
解决方案
闪烁问题的核心是SSR与CSR阶段认证状态不一致,以下是几种适配Angular函数式守卫的可行方案:
1. 利用TransferState同步前后端认证状态
Angular的TransferState可在SSR阶段将服务端获取的认证状态传递到客户端,避免前后端判定结果冲突。
实现步骤:
- 在
AuthService中引入依赖:
import { TransferState, makeStateKey } from '@angular/platform-browser'; import { PLATFORM_ID, inject } from '@angular/core'; import { isPlatformServer } from '@angular/common'; const AUTH_STATE_KEY = makeStateKey<boolean>('isAuthenticated');
- 修改
checkAuthentication方法,适配前后端逻辑:
constructor(private transferState: TransferState) {} checkAuthentication(): Observable<boolean> { // 客户端优先读取TransferState中的状态 if (this.transferState.hasKey(AUTH_STATE_KEY)) { const isAuthenticated = this.transferState.get(AUTH_STATE_KEY, false); this.transferState.remove(AUTH_STATE_KEY); return of(isAuthenticated); } // 服务端逻辑:从cookie读取token并验证(需登录时将token存入cookie) if (isPlatformServer(inject(PLATFORM_ID))) { const token = this.getServerCookie('access_token'); // 实现服务端读取cookie的方法 const isAuthenticated = token && !AuthUtils.isTokenExpired(token); this.transferState.set(AUTH_STATE_KEY, isAuthenticated); return of(isAuthenticated); } // 客户端正常逻辑 const isAuthenticated = this.accessToken && !AuthUtils.isTokenExpired(this.accessToken); return of(isAuthenticated); } // 服务端读取cookie的辅助方法(需在server.ts中注入request对象,或使用Angular Universal的cookie工具) private getServerCookie(name: string): string | null { // 此处需根据你的SSR环境实现,比如从Express的request中读取cookie const request = inject(Request); return request.cookies[name] || null; }
- 登录时同步token到cookie:
在signIn方法中添加:
document.cookie = `access_token=${response.token}; path=/; secure; SameSite=Strict`;
2. 服务端跳过认证判定,由客户端统一处理
修改AuthGuard,让服务端直接放行,待客户端初始化完成后再执行认证逻辑,避免服务端错误渲染登录页。
import { PLATFORM_ID, inject } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; export const AuthGuard: CanActivateFn | CanActivateChildFn = (route, state) => { const router: Router = inject(Router); const authService = inject(AuthService); const platformId = inject(PLATFORM_ID); if (isPlatformBrowser(platformId)) { return authService.checkAuthentication().pipe( switchMap((isAuthenticated) => { if (!isAuthenticated) { return of(router.createUrlTree(['auth/sign-in'], { queryParams: { param: state.url } })); } return of(true); }) ); } else { // 服务端直接返回true,客户端初始化后会重新验证 return of(true); } };
注意:此方案服务端会渲染目标页面(即使用户未认证),客户端初始化后会重定向到登录页,适合对SSR权限控制要求不高的场景。
3. 改用Cookie存储token替代sessionStorage
将token存储在HttpOnly Cookie中,让服务端和客户端都能读取,确保前后端认证状态完全一致。
修改AuthService的存储逻辑:
// 保存token saveAccessToken(token: string): void { document.cookie = `access_token=${token}; path=/; secure; SameSite=Strict`; } // 获取token getAccessToken(): string | null { const value = `; ${document.cookie}`; const parts = value.split(`; access_token=`); if (parts.length === 2) return parts.pop()?.split(';').shift() || null; return null; }
同时更新checkAuthentication方法,直接从Cookie读取token验证即可。
内容的提问来源于stack exchange,提问作者MH Prince
相关产品推荐
相关产品推荐

