Summary
This incident involved an Angular 21 application where a CanActivate guard incorrectly redirected authenticated users when navigating directly to a route (e.g., /home). The issue occurred only during Server-Side Rendering (SSR), even though a valid token existed in localStorage.
Root Cause
The guard relied on localStorage, which is not available during SSR. Because Angular executes guards on the server during initial navigation, the condition isPlatformBrowser(id) returned false on the server, causing the guard to always redirect.
Key points:
- SSR cannot access browser APIs such as
localStorage,sessionStorage, orwindow. - The guard returned a redirect tree on the server, so the client never got a chance to evaluate the real token.
Why This Happens in Real Systems
This pattern is common in SSR-enabled Angular apps because:
- Guards run both on the server and the browser.
- Developers often assume guards run only in the browser.
- Browser-only APIs silently fail or return empty values during SSR.
Typical triggers:
- Direct URL navigation
- Page refreshes
- Deep linking to protected routes
Real-World Impact
This failure mode causes:
- Infinite redirect loops between SSR and client navigation
- Users being logged out unexpectedly
- SEO crawlers receiving incorrect redirects
- Broken deep links for authenticated pages
Example or Code (if necessary and relevant)
A corrected guard pattern for SSR-aware authentication:
import { isPlatformBrowser } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
export const authGuard: CanActivateFn = () => {
const platformId = inject(PLATFORM_ID);
const router = inject(Router);
if (!isPlatformBrowser(platformId)) {
return true;
}
const token = localStorage.getItem('utoken');
return token ? true : router.createUrlTree(['/login']);
};
How Senior Engineers Fix It
Experienced engineers avoid browser-only logic during SSR by:
- Short‑circuiting guards on the server (
return true) - Using transfer state or cookies instead of
localStorage - Implementing universal-safe authentication services
- Ensuring guards behave idempotently across server and client
- Logging SSR vs browser execution paths to detect mismatches early
Why Juniors Miss It
This issue is subtle because:
- SSR runs guards before the browser ever loads, which is not obvious.
localStorageworks perfectly in development without SSR.- Angular hides SSR execution unless explicitly logged.
- The redirect appears correct at first glance, masking the real cause.
A junior developer typically assumes:
- Guards run only in the browser
localStorageis always available- SSR behaves like a normal client-side app
If you’d like, I can expand this into a full internal postmortem template you can reuse for your engineering team.