Skip to main content

Overview

The @angular/platform-server package provides the infrastructure for rendering Angular applications on the server. This enables server-side rendering (SSR) for improved performance, SEO, and social media sharing.

Rendering Functions

renderApplication

Bootstraps and renders a standalone Angular application to an HTML string. This is the recommended approach for SSR with standalone components.
function renderApplication<T>(
  bootstrap: (context: BootstrapContext) => Promise<ApplicationRef>,
  options: {
    document?: string | Document
    url?: string
    platformProviders?: Provider[]
  }
): Promise<string>
bootstrap
(context: BootstrapContext) => Promise<ApplicationRef>
required
Function that bootstraps the application. Receives a BootstrapContext with the platform reference.
options
object
required
Configuration options for the render operation.
options.document
string | Document
The HTML document to render into, either as a string or Document instance. Defaults to a minimal HTML template.
options.url
string
The URL for the current render request. Used for routing and generating absolute URLs.
options.platformProviders
Provider[]
Additional platform-level providers for the render operation.
Returns: Promise<string> - The rendered HTML as a string. Example:
import { bootstrapApplication } from '@angular/platform-browser';
import { renderApplication } from '@angular/platform-server';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';

export async function render(url: string, document: string) {
  const html = await renderApplication(
    (context) => bootstrapApplication(AppComponent, {
      ...config,
      providers: [
        ...config.providers || []
      ]
    }, context),
    {
      document,
      url
    }
  );
  
  return html;
}
Express Integration:
import express from 'express';
import { renderApplication } from '@angular/platform-server';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './src/app/app.component';
import { readFileSync } from 'fs';
import { join } from 'path';

const app = express();
const indexHtml = readFileSync(join(process.cwd(), 'dist/browser/index.html'), 'utf-8');

app.get('*', async (req, res) => {
  try {
    const html = await renderApplication(
      (context) => bootstrapApplication(AppComponent, appConfig, context),
      {
        document: indexHtml,
        url: req.url
      }
    );
    
    res.send(html);
  } catch (error) {
    console.error('SSR error:', error);
    res.status(500).send('Server error');
  }
});

app.listen(4000);
renderApplication automatically waits for the application to stabilize before rendering. This ensures all async operations complete before generating the HTML.

renderModule

Bootstraps and renders an NgModule-based Angular application to an HTML string. Use this for applications using NgModules instead of standalone components.
function renderModule<T>(
  moduleType: Type<T>,
  options: {
    document?: string | Document
    url?: string
    extraProviders?: StaticProvider[]
  }
): Promise<string>
moduleType
Type<T>
required
The NgModule class to bootstrap.
options
object
required
Configuration options for the render operation.
options.document
string | Document
The HTML document to render into.
options.url
string
The URL for the current render request.
options.extraProviders
StaticProvider[]
Additional platform-level providers.
Returns: Promise<string> - The rendered HTML as a string. Example:
import { renderModule } from '@angular/platform-server';
import { AppServerModule } from './app/app.server.module';
import { readFileSync } from 'fs';

const indexHtml = readFileSync('./dist/browser/index.html', 'utf-8');

export async function render(url: string) {
  const html = await renderModule(AppServerModule, {
    document: indexHtml,
    url,
    extraProviders: [
      // Additional providers if needed
    ]
  });
  
  return html;
}

Platform

platformServer

Creates a server platform instance for Angular applications. This is used internally by renderApplication and renderModule but can be used directly for advanced scenarios.
function platformServer(
  extraProviders?: StaticProvider[]
): PlatformRef
extraProviders
StaticProvider[]
Additional platform-level providers.
Returns: PlatformRef - Server platform reference. Example:
import { platformServer } from '@angular/platform-server';
import { AppServerModule } from './app/app.server.module';

const platform = platformServer();
const moduleRef = await platform.bootstrapModule(AppServerModule);

// Access application services
const appRef = moduleRef.injector.get(ApplicationRef);
await appRef.whenStable();

// Clean up
platform.destroy();

ServerModule

NgModule that provides server-specific implementations for Angular’s browser APIs. Import this in your server-specific app module.
@NgModule({
  exports: [BrowserModule]
})
class ServerModule {}
Example:
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    AppModule,
    ServerModule
  ],
  bootstrap: [AppComponent]
})
export class AppServerModule {}
For standalone applications, use provideServerRendering() instead of ServerModule.

Providers

provideServerRendering

Provides server-side rendering configuration for standalone applications. Use this in your server application configuration.
function provideServerRendering(): EnvironmentProviders
Example:
import { ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(),
    provideRouter(routes)
  ]
};
Complete Server Setup:
// app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);

Services

PlatformState

Service that provides access to the server-rendered document and rendering utilities.
@Injectable()
class PlatformState {
  getDocument(): Document
  renderToString(): string
}
Methods:
getDocument
() => Document
Returns the server-side Document instance.
renderToString
() => string
Renders the current document to an HTML string.
Example:
import { Injectable, inject } from '@angular/core';
import { PlatformState } from '@angular/platform-server';

@Injectable()
export class ServerService {
  private platformState = inject(PlatformState);

  getDocumentTitle(): string {
    const doc = this.platformState.getDocument();
    return doc.title;
  }

  addMetaTag(name: string, content: string): void {
    const doc = this.platformState.getDocument();
    const meta = doc.createElement('meta');
    meta.setAttribute('name', name);
    meta.setAttribute('content', content);
    doc.head.appendChild(meta);
  }
}

Configuration Tokens

INITIAL_CONFIG

Injection token for providing initial server configuration.
const INITIAL_CONFIG: InjectionToken<PlatformConfig>
PlatformConfig Interface:
interface PlatformConfig {
  document?: string | Document
  url?: string
}
Example:
import { INITIAL_CONFIG } from '@angular/platform-server';
import { platformServer } from '@angular/platform-server';

const platform = platformServer([
  {
    provide: INITIAL_CONFIG,
    useValue: {
      document: '<html><body><app-root></app-root></body></html>',
      url: 'https://example.com/page'
    }
  }
]);

BEFORE_APP_SERIALIZED

Injection token for callbacks that run before the application is serialized to HTML. Useful for finalizing state or performing cleanup.
const BEFORE_APP_SERIALIZED: InjectionToken<Array<() => void | Promise<void>>>
Example:
import { BEFORE_APP_SERIALIZED } from '@angular/platform-server';
import { Provider } from '@angular/core';

export const serverCallbackProvider: Provider = {
  provide: BEFORE_APP_SERIALIZED,
  useFactory: () => {
    return () => {
      console.log('About to serialize application');
      // Perform cleanup or state finalization
    };
  },
  multi: true
};
Async Callbacks:
import { BEFORE_APP_SERIALIZED } from '@angular/platform-server';
import { inject } from '@angular/core';
import { DataService } from './data.service';

export const asyncCallbackProvider = {
  provide: BEFORE_APP_SERIALIZED,
  useFactory: () => {
    const dataService = inject(DataService);
    return async () => {
      // Wait for pending operations
      await dataService.flushCache();
    };
  },
  multi: true
};

Server-Side Rendering Setup

Basic SSR Setup

For a complete SSR setup with standalone components: 1. Install dependencies:
ng add @angular/ssr
2. Create server configuration (app.config.server.ts):
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);
3. Create server entry point (main.server.ts):
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';

const bootstrap = (context) => bootstrapApplication(AppComponent, config, context);

export default bootstrap;
4. Create Express server:
import express from 'express';
import { renderApplication } from '@angular/platform-server';
import bootstrap from './main.server';
import { readFileSync } from 'fs';

const app = express();
const indexHtml = readFileSync('./dist/browser/index.html', 'utf-8');

app.get('*', async (req, res) => {
  const html = await renderApplication(bootstrap, {
    document: indexHtml,
    url: req.url
  });
  res.send(html);
});

app.listen(4000, () => {
  console.log('Server running on http://localhost:4000');
});

Transfer State

Transfer state allows data to be shared between server and client, avoiding duplicate HTTP requests.
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideServerRendering } from '@angular/platform-server';

// Client config
export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration()
  ]
};

// Server config
export const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};
Using TransferState Service:
import { Component, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { TransferState, makeStateKey } from '@angular/core';
import { HttpClient } from '@angular/common/http';

const DATA_KEY = makeStateKey<any>('userData');

@Component({
  selector: 'app-user',
  template: '<div>{{ userData | json }}</div>'
})
export class UserComponent {
  private transferState = inject(TransferState);
  private http = inject(HttpClient);
  private platformId = inject(PLATFORM_ID);
  userData: any;

  ngOnInit() {
    // Check if data is already in transfer state
    const cachedData = this.transferState.get(DATA_KEY, null);
    
    if (cachedData) {
      // Use cached data from server
      this.userData = cachedData;
    } else {
      // Fetch data (happens on server)
      this.http.get('/api/user').subscribe(data => {
        this.userData = data;
        
        // Store in transfer state if on server
        if (isPlatformServer(this.platformId)) {
          this.transferState.set(DATA_KEY, data);
        }
      });
    }
  }
}
When using provideHttpClient() with withFetch(), transfer state is automatically handled for HTTP requests.

Best Practices

1. Handle Platform Differences

Use platform checks for code that should only run on server or browser:
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID, inject } from '@angular/core';

export class MyComponent {
  private platformId = inject(PLATFORM_ID);

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      // Browser-only code (e.g., accessing window, localStorage)
      console.log('Running in browser');
    }
    
    if (isPlatformServer(this.platformId)) {
      // Server-only code
      console.log('Running on server');
    }
  }
}

2. Avoid Browser-Only APIs

Avoid direct references to browser globals like window, document, localStorage:
import { DOCUMENT } from '@angular/common';
import { inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

export class SafeComponent {
  private document = inject(DOCUMENT);
  private platformId = inject(PLATFORM_ID);

  getWindowWidth(): number {
    if (isPlatformBrowser(this.platformId)) {
      return window.innerWidth;
    }
    return 0; // Default for server
  }

  setLocalStorage(key: string, value: string): void {
    if (isPlatformBrowser(this.platformId)) {
      localStorage.setItem(key, value);
    }
  }
}

3. Optimize Performance

Use BEFORE_APP_SERIALIZED for cleanup and optimization:
import { BEFORE_APP_SERIALIZED } from '@angular/platform-server';

export const cleanupProvider = {
  provide: BEFORE_APP_SERIALIZED,
  useFactory: () => {
    return () => {
      // Remove temporary data
      // Close connections
      // Finalize state
    };
  },
  multi: true
};

4. Handle Absolute URLs

Ensure URLs are absolute when rendering on the server:
import { Injectable, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';

@Injectable({ providedIn: 'root' })
export class UrlService {
  private document = inject(DOCUMENT);

  getAbsoluteUrl(path: string): string {
    const baseUrl = this.document.location.origin;
    return `${baseUrl}${path}`;
  }
}

@angular/platform-browser

Browser platform APIs and client hydration

TransferState

Share data between server and client

HttpClient

HTTP requests with SSR support

Router

Routing in SSR applications