HTML on the First Hit, JS After
Server-Side Rendering
`ng add @angular/ssr` wires up an Express-based render server so the first request returns finished HTML while the JS bundle loads in the background.
What you'll learn
- Add SSR with a single ng add command
- Understand main.server.ts vs main.ts
- Know what breaks under SSR (window, document, localStorage)
Client-only Angular has a problem: the first byte from the server is a near-empty HTML shell, and users stare at a blank screen until the JS bundle arrives, parses, and renders. Server-side rendering fixes that.
Add SSR
ng add @angular/ssr The schematic generates server.ts, main.server.ts, and updates angular.json with a server build target. Two entry points now exist:
main.ts— boots Angular in the browsermain.server.ts— boots Angular in Node so it can render to a string
Build and Run
ng build
node dist/my-app/server/server.mjs A request to / runs your component tree through renderApplication, returns HTML, and the static asset middleware serves the JS/CSS afterwards.
What Breaks Under SSR
Node doesn’t have window, document, or localStorage. Code that touches them throws when rendered on the server.
import { Component, PLATFORM_ID, inject, afterNextRender } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({ selector: 'app-x', standalone: true, template: `` })
export class XComponent {
private isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
constructor() {
if (this.isBrowser) {
// safe to touch window here
}
afterNextRender(() => {
// even safer — runs once, on the browser only, after the first render
});
}
} afterNextRender is the modern idiom for browser-only effects — measuring the DOM, third-party scripts, anything that needs a real browser.