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.
Configuration options for the render operation.
The HTML document to render into, either as a string or Document instance. Defaults to a minimal HTML template.
The URL for the current render request. Used for routing and generating absolute URLs.
options.platformProviders
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 >
The NgModule class to bootstrap.
Configuration options for the render operation.
The HTML document to render into.
The URL for the current render request.
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 ;
}
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
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
Service that provides access to the server-rendered document and rendering utilities.
@ Injectable ()
class PlatformState {
getDocument () : Document
renderToString () : string
}
Methods:
Returns the server-side Document instance.
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:
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
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 );
}
}
}
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