Angular 14 Server-Side Rendering Demystified with Angular Universal

May 3, 2022Programming

Angular 14's SSR with Angular Universal transforms how apps render. Understand the request flow, hydration process, and Express server setup. Optimize performance and avoid common pitfalls in your server-side rendered applications.

Setting Up Angular Universal with Angular 14

First, make sure your Angular 14 project includes Angular Universal support. Use the Angular CLI to add it:

ng add @nguniversal/express-engine

This command sets up everything you need, including a server module, Express server, and TypeScript configuration.

server.ts - Basic Express Setup

import 'zone.js/node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';

const app = express();

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModule,
}));

app.set('view engine', 'html');
app.set('views', join(__dirname, 'browser'));

app.get('*', (req, res) => {
  res.render('index', { req });
});

app.listen(4000, () => {
  console.log(`Node server listening on http://localhost:4000`);
});

Under the Hood: SSR Lifecycle Explained

When a request comes in, the Universal engine processes it using the following steps:

  1. ngExpressEngine() wraps Angular’s CommonEngine.
  2. Each request calls CommonEngine.render().
  3. render() internally calls Angular’s renderModule() from @angular/platform-server.

SSR Flow in Angular Universal

renderModule(AppServerModule, {
  document: indexHtml,
  url: req.url
}).then(html => res.send(html));
  • A new PlatformRef is created.
  • Angular bootstraps the app module.
  • It waits for the app to become stable (i.e. all async tasks complete).
  • The DOM (powered by the Domino library) is serialized into HTML and sent back.

Application Stability Before Rendering

Angular checks when the app is stable using ApplicationRef.isStable. Only after all pending async tasks are done does it continue rendering:

appRef.isStable
  .pipe(first(isStable => isStable))
  .subscribe(() => serializeDocument());

This guarantees that HTTP-loaded data is rendered and visible in the HTML response.


Cleanup: Destroying the App

After rendering is complete:

  • PlatformRef.destroy() is called.
  • App module, root component, and all services are torn down.
  • All ngOnDestroy() lifecycle hooks are invoked.

This frees memory and ensures no SSR state leaks into other requests.


Gotchas and Best Practices

Pending Async Tasks → Memory Leaks

If a request never completes (e.g. hanging HTTP call), the app never stabilizes. This prevents rendering and causes memory leaks.

Shared Global State → Race Conditions

Avoid using global variables or static state. SSR renders apps in parallel, and shared mutable state can lead to race conditions.

Missing Unsubscribe → Memory Waste

Make sure to clean up all RxJS subscriptions:

ngOnDestroy() {
  this.subscription?.unsubscribe();
}

Even services should handle teardown logic to avoid server memory bloat.


Summary

  • Use @nguniversal/express-engine for Angular 14 SSR.
  • Rendering uses renderModule() from @angular/platform-server.
  • Angular waits until app stability before HTML serialization.
  • Always clean up subscriptions and avoid shared state.

SSR in Angular 14 with Universal gives you faster first-paint times and better SEO. But it requires precise control over lifecycle management and asynchronous logic.

Master these fundamentals, and your server-rendered Angular apps will scale beautifully.

Would you like to stay updated with my latest posts?


No spam, No third-party sharing. Just between us

Recent

A fun demo project experimenting with Spring AI and MCP Server implementation
Spring AI MCP Server - A Fun Demo Project
A fun demo project experimenting with Spring AI and MCP Server implementation
Discover my journey with the Eclipson Model A RC Controlled Airplane, from 3D printing to its maiden flight.
3D Printing Eclipson Model A RC Controlled Airplane
Discover my journey with the Eclipson Model A RC Controlled Airplane, from 3D printing to its maiden flight.
How Portainer transformed my home server management with its intuitive Docker container interface and powerful features
Discovering Portainer - A Game Changer for My Home Server
How Portainer transformed my home server management with its intuitive Docker container interface and powerful features