I'm developing an Angular (version 20) app with server side rendering. In my app.config.ts I use provideAppInitializer to initialize some data before loading: provideAppInitializer(() => { const appService = inject(AppService); return appService.init(); }) The code for the init function dispatches an event that performs a side effect to get the data: init(): Promise<void> { return new Promise((resolve) => { this.store.dispatch(FreelancerActions.getFreelancer()); this.store.select(selectOnGetFreelancer) .pipe( filter((onGetFreelancer) => ['success', 'error'].includes(onGetFreelancer),), take(1), ) .subscribe(() => resolve()); }); } Here's the effect: getFreelancer = createEffect(() => this.actions$.pipe( ofType(FreelancerActions.getFreelancer), exhaustMap(() => this.freelancerService.getMe().pipe( map((freelancer: Freelancer) => FreelancerActions.getFreelancerSuccess({ freelancer }), ), catchError(() => of(FreelancerActions.getFreelancerError()) ), ), ), ), ); Now I have the following guard: import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { map } from 'rxjs'; import { AppState } from '../../../shared/store/app.state'; import { selectFreelancer } from '../../../shared/store/freelancer/freelancer.selectors'; export const unauthGuard: CanActivateFn = (route, state) => { const store = inject(Store<AppState>); const router = inject(Router); return store.select(selectFreelancer).pipe( map((freelancer) => { if (freelancer === null) { return true; } router.navigate(['/']); return false; }), ); }; This guard works almost properly: it redirects to / if freelancer != null. For example if I enter /auth/login in the URL and freelancer != null I get redirected to / which is the expected behavior. However, before getting redirected the /auth/login page is displayed quickly and I don't want that. Is there a way to fix this? Note: ChatGPT suggested me to use an if statement around the router outlet not to display anything until before the data is loaded. I personally don't want to do that as it breaks SEO. ChatGPT answers (doesn't work) ChatGPT I tried to solve the issue with the help of ChatGPT but it didn't work. Here's the answer that I got: Update your reducer’s initial state Change the initial value of freelancer from null → undefined, so we can tell “not loaded yet” apart from “loaded but no freelancer”. // freelancer.reducer.ts export interface FreelancerState { freelancer: Freelancer | null | undefined; // ... } export const initialState: FreelancerState = { freelancer: undefined, // <-- important change // ... }; (Optional) Update selector to explicitly return this type // freelancer.selectors.ts export const selectFreelancer = (state: AppState) => state.freelancer.freelancer; // now possibly undefined | null | Freelancer Update your unauthGuard to wait until loaded Only act once freelancer becomes something other than undefined: export const unauthGuard: CanActivateFn = (route, state) => { const store = inject(Store<AppState>); const router = inject(Router); return store.select(selectFreelancer).pipe( filter((freelancer) => freelancer !== undefined), // skip until init finished take(1), map((freelancer) => { if (freelancer === null) { // no freelancer => allow navigation to /auth/* return true; } // has freelancer => redirect away router.navigate(['/']); return false; }), ); }; Me Your solution doesn't work ChatGPT Got it — thanks for trying it. If the guard is still showing the page before the redirect, even after using the filter(... !== undefined) trick, then it means Angular is displaying the route component while the guard is still pending — this happens because CanActivate only blocks navigation after the first emission. Until then, Angular assumes it will succeed. ✅ Recommended fix: use canMatch instead of canActivate canMatch is evaluated before the route is even instantiated, so your login page won’t be created/rendered until the guard fully resolves. Here’s how to rewrite your guard as a CanMatchFn: import { inject } from '@angular/core'; import { CanMatchFn, Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { filter, map, take } from 'rxjs'; import { AppState } from '../../../shared/store/app.state'; import { selectFreelancer } from '../../../shared/store/freelancer/freelancer.selectors'; export const unauthGuard: CanMatchFn = (route, segments) => { const store = inject(Store<AppState>); const router = inject(Router); return store.select(selectFreelancer).pipe( filter((freelancer) => freelancer !== undefined), // wait for init take(1), map((freelancer) => { if (freelancer === null) { // no user => allow matching return true; } // user exists => block and redirect router.navigate(['/']); return false; }) ); }; Apply it in your routing like this: { path: 'auth', canMatch: [unauthGuard], // ← use canMatch instead of canActivate loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule) } Me It still doesn't work even with canMatch. ChatGPT Understood — if canMatch is still allowing the component to render before the user is loaded, the root cause is probably not the guard, but rather that the router is performing initial navigation before your APP_INITIALIZER Promise has resolved. In Angular with SSR, initial navigation happens as soon as the app bootstraps, even if your initializer is still running — so guards run with an empty store, and rendering begins. ✅ Solution: tell Angular to wait for APP_INITIALIZER before starting routing Turn on blocking initial navigation in your app bootstrap: bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withEnabledBlockingInitialNavigation()), ... ] }); What this does Blocks initialNavigation until all APP_INITIALIZERs are resolved Then runs your canMatch guard after the store has been populated Prevents any route component (like /auth/login) from being instantiated or rendered until your initialization is done → ✅ no flicker ✅ Checklist to fix your issue: ✔ Your init() function returns a true Promise (already done) ✔ Use canMatch guard with filter(... !== undefined) (already done) ➡ Add withEnabledBlockingInitialNavigation() // main.ts (or wherever you bootstrap your app) import { withEnabledBlockingInitialNavigation } from '@angular/router'; bootstrapApplication(AppComponent, { providers: [ provideRouter( routes, withEnabledBlockingInitialNavigation() ), // other providers… ] }); Would you like me to paste your full main.ts or app.config.ts example showing exactly how to insert that line? Me It still doesn't work even with withEnabledBlockingInitialNavigation Thanks for confirming — since all the usual mechanisms (APP_INITIALIZER, canMatch, withEnabledBlockingInitialNavigation) are already in place and the /auth/login component still briefly renders, what you’re running into is the way Angular SSR hydrates on the client after it has already rendered the initial HTML from the server. In other words: On the server, Angular renders /auth/login (because during SSR the store is empty: freelancer = undefined/null) That HTML is sent to the browser On the client, when Angular bootstraps, your effect runs, populates the store, the guard kicks in and redirects ✅ But the initial HTML for /auth/login is already painted on the screen, causing the flicker ✅ The SSR-safe solution: don't render the login route on the server until initialized You can do this by adding an ngIf to your or wrapping it in a resolver component that waits until initialization is done on the server and the browser. Option that works well: Wrap your router-outlet: <!-- app.component.html --> <ng-container *ngIf="initialized"> <router-outlet></router-outlet> </ng-container> Inside your AppComponent: export class AppComponent { initialized = false; constructor(private appService: AppService) {} ngOnInit() { this.appService.init().then(() => { this.initialized = true; }); } } Now nothing is rendered (even on SSR) until the init() promise resolves → so /auth/login never appears if the user is authenticated. ⚠ Important note Remove your init logic from APP_INITIALIZER when you do this, otherwise it will run twice (once in initializer and once in AppComponent.ngOnInit()). TL;DR working setup Remove provideAppInitializer In AppComponent.ngOnInit() call your current init() method Wrap in *ngIf="initialized" Keep your canMatch guard for navigation once the app is “running” Continue reading...