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 defined
  • navigator is not defined
  • localStorage 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 window checks
  • 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 window checks
  • Flexible: Choose the right method for your context

4. Type Safety

  • Better TypeScript integration
  • Proper type guards
  • Reduced runtime errors

Usage Guidelines

@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
}
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

  1. Angular Universal Testing: Test with actual SSR scenarios
  2. Performance: Monitor performance impact of platform detection
  3. SSR Optimizations: Explore server-side specific optimizations
  4. 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.