SSR/SSG Safe Implementation
March 27, 2026 · View on GitHub
Overview
This document describes the implementation of SSR/SSG safe patterns for the browser-web-apis package to prevent errors during server-side rendering and static site generation using @angular/ssr for robust platform detection.
Problem Statement
Browser APIs like window, navigator, localStorage, etc. are not available in server environments, causing errors like:
window is not definednavigator is not definedlocalStorage is not defined
Solution Architecture
1. Enhanced SsrSafeUtil with @angular/ssr Integration
Created a comprehensive utility that combines traditional detection with Angular's platform detection:
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
export class SsrSafeUtil {
// Traditional detection (fallback)
static isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
// Angular-based detection (preferred, requires injection context)
static isAngularBrowser(): boolean {
try {
const platformId = inject(PLATFORM_ID);
return isPlatformBrowser(platformId);
} catch {
return this.isBrowser();
}
}
static isAngularServer(): boolean {
try {
const platformId = inject(PLATFORM_ID);
return isPlatformServer(platformId);
} catch {
return this.isServer();
}
}
}
2. Injectable SsrSafeService
Added an injectable service for contexts where DI is available:
@Injectable({ providedIn: 'root' })
export class SsrSafeService {
private platformId = inject(PLATFORM_ID);
get isBrowser(): boolean {
return isPlatformBrowser(this.platformId);
}
get isServer(): boolean {
return isPlatformServer(this.platformId);
}
get window(): Window | null {
return this.isBrowser ? window : null;
}
get navigator(): Navigator | null {
return this.isBrowser ? navigator : null;
}
}
3. BrowserApiBaseService Enhancement
Enhanced the base service to use Angular's platform detection:
export abstract class BrowserApiBaseService {
protected platformId = inject(PLATFORM_ID);
protected isBrowserEnvironment(): boolean {
return isPlatformBrowser(this.platformId);
}
protected isServerEnvironment(): boolean {
return isPlatformServer(this.platformId);
}
protected async initialize(): Promise<void> {
if (this.isServerEnvironment()) {
this.logWarning(`${this.getApiName()} API not available in server environment`);
return;
}
// ... rest of implementation
}
}
Service-Specific Changes with @angular/ssr
BatteryService
Updated to use Angular's platform detection:
@Injectable()
export class BatteryService {
private platformId = inject(PLATFORM_ID);
private isBrowserEnvironment(): boolean {
return isPlatformBrowser(this.platformId);
}
private isServerEnvironment(): boolean {
return isPlatformServer(this.platformId);
}
private async initBattery(): Promise<void> {
if (this.initialized || !this.isBrowserEnvironment()) {
return;
}
// ... rest of implementation
}
isSupported(): boolean {
return this.isBrowserEnvironment() && 'getBattery' in navigator;
}
}
Implementation Patterns with @angular/ssr
1. Primary Pattern: Angular Platform Detection
// In services with DI
protected platformId = inject(PLATFORM_ID);
protected isBrowserEnvironment(): boolean {
return isPlatformBrowser(this.platformId);
}
protected isServerEnvironment(): boolean {
return isPlatformServer(this.platformId);
}
2. Static Methods with Fallback
// For contexts without DI
static isAngularBrowser(): boolean {
try {
const platformId = inject(PLATFORM_ID);
return isPlatformBrowser(platformId);
} catch {
return this.isBrowser(); // Fallback to traditional detection
}
}
3. Injectable Service Pattern
@Injectable({ providedIn: 'root' })
export class SsrSafeService {
private platformId = inject(PLATFORM_ID);
get isBrowser(): boolean {
return isPlatformBrowser(this.platformId);
}
}
4. Safe API Access
// Use platform detection before accessing browser APIs
if (this.isBrowserEnvironment()) {
// Safe to access window, navigator, etc.
const result = navigator.someAPI;
}
Benefits of @angular/ssr Integration
1. More Reliable Detection
- Uses Angular's internal platform detection
- Handles edge cases better than simple
typeof windowchecks - Consistent with Angular's own SSR handling
2. Framework Integration
- Aligns with Angular's platform detection patterns
- Works seamlessly with Angular Universal
- Future-proof as Angular evolves
3. Multiple Detection Strategies
- Primary: Angular's
isPlatformBrowser()/isPlatformServer() - Fallback: Traditional
typeof windowchecks - Flexible: Choose the right method for your context
4. Type Safety
- Better TypeScript integration
- Proper type guards
- Reduced runtime errors
Usage Guidelines
For Services with DI (Recommended)
@Injectable()
export class MyService {
private platformId = inject(PLATFORM_ID);
constructor() {
if (isPlatformBrowser(this.platformId)) {
// Browser-specific initialization
}
}
}
For Static Contexts
export class MyUtil {
static doSomething() {
if (SsrSafeUtil.isAngularBrowser()) {
// Browser-specific code
}
}
}
For Components
@Component({...})
export class MyComponent {
constructor(private ssrSafe: SsrSafeService) {
if (this.ssrSafe.isBrowser) {
// Browser-specific code
}
}
}
Migration from Traditional Detection
Before
if (typeof window !== 'undefined') {
// Browser code
}
After (Recommended)
const platformId = inject(PLATFORM_ID);
if (isPlatformBrowser(platformId)) {
// Browser code
}
After (Fallback)
if (SsrSafeUtil.isAngularBrowser()) {
// Browser code
}
Testing Results
- ✅ Build successful with @angular/ssr integration
- ✅ All tests passing
- ✅ No TypeScript errors
- ✅ Enhanced SSR/SSG compatibility
- ✅ More reliable platform detection
Future Considerations
- Angular Universal Testing: Test with actual SSR scenarios
- Performance: Monitor performance impact of platform detection
- SSR Optimizations: Explore server-side specific optimizations
- Edge Cases: Handle hybrid rendering scenarios
Conclusion
The integration with @angular/ssr provides a more robust and reliable foundation for SSR/SSG safe operations. The combination of Angular's platform detection with traditional fallbacks ensures maximum compatibility while maintaining the benefits of framework-integrated detection.
This approach is production-ready and follows Angular's best practices for SSR/SSG applications.