On this page

SSR, pre-rendering, and deployment

15 min read TextCh. 5 — Production

What is SSR?

Server-Side Rendering (SSR) generates your application's HTML on the server before sending it to the browser. Benefits:

  • Improved SEO — Search bots read complete HTML
  • Fast first load — The user sees content immediately
  • Social sharing — Meta tags available for Open Graph

Render modes in Angular 21

Angular 21 defines three modes in ServerRoute:

Mode Description When to use
RenderMode.Prerender HTML generated at build time Static pages (home, about, blog)
RenderMode.Server HTML generated per request Dynamic pages (profiles, search)
RenderMode.Client Rendered only in the browser Dashboard, admin, private content

Configuring SSR

When creating a project with ng new, Angular asks if you want SSR. If you already have a project, add it with:

ng add @angular/ssr

This generates:

  • src/app/app.config.server.ts — Server configuration
  • src/app/app.routes.server.ts — Routes with render modes
  • server.ts — Express server

Server routes

Define how each route is rendered:

import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  // Pre-render at build time
  { path: '', renderMode: RenderMode.Prerender },
  { path: 'blog', renderMode: RenderMode.Prerender },

  // Pre-render with parameters (generates one HTML per slug)
  {
    path: 'blog/:slug',
    renderMode: RenderMode.Prerender,
    async getPrerenderParams() {
      return [
        { slug: 'intro-to-angular' },
        { slug: 'signals-tutorial' },
        { slug: 'ssr-complete-guide' },
      ];
    },
  },

  // Dynamic SSR
  { path: 'search', renderMode: RenderMode.Server },

  // Client-only
  { path: 'dashboard/**', renderMode: RenderMode.Client },
];

Platform detection

Some APIs only exist in the browser. Use isPlatformBrowser to protect that code:

import { isPlatformBrowser, PLATFORM_ID } from '@angular/common';
import { inject } from '@angular/core';

const platformId = inject(PLATFORM_ID);

if (isPlatformBrowser(platformId)) {
  // Safe: we are in the browser
  window.scrollTo(0, 0);
  localStorage.setItem('key', 'value');
}

afterNextRender and afterRender

For code that should only run after rendering in the browser:

import { afterNextRender } from '@angular/core';

constructor() {
  afterNextRender(() => {
    // Runs once, only in the browser
    // Ideal for initializing third-party libraries
    this.chart = new Chart(this.canvasRef.nativeElement, config);
  });
}

Meta tags for SEO

Use the Title and Meta services to set meta tags server-side:

import { Title, Meta } from '@angular/platform-browser';

@Component({ /* ... */ })
export class BlogPost {
  private readonly title = inject(Title);
  private readonly meta = inject(Meta);

  constructor() {
    effect(() => {
      const post = this.post();
      this.title.setTitle(`${post.title} | My Blog`);
      this.meta.updateTag({ name: 'description', content: post.summary });
      this.meta.updateTag({ property: 'og:title', content: post.title });
      this.meta.updateTag({ property: 'og:image', content: post.image });
    });
  }
}

Deployment

Firebase Hosting (static + Cloud Functions)

ng build
firebase deploy

Docker (full SSR)

FROM node:20-alpine
WORKDIR /app
COPY dist/ ./dist/
COPY package.json ./
RUN npm install --production
EXPOSE 4000
CMD ["node", "dist/my-app/server/server.mjs"]

Compatible platforms

  • Firebase Hosting — Static pre-rendering + Cloud Functions
  • Vercel — Automatic serverless SSR
  • Netlify — Static pre-rendering
  • Railway / Render — SSR with Docker

Practice

  1. Configure render modes: Define an app.routes.server.ts file with at least 3 routes using all three modes: Prerender for the home page, Server for dynamic routes, and Client for the dashboard.
  2. Protect browser APIs: Create a service that uses localStorage. Wrap it with isPlatformBrowser() so it does not fail during SSR, and add a server-side fallback.
  3. Add SEO meta tags: Use the Title and Meta services inside an effect() to dynamically set the page title and description and og:title tags in a component.

In the next lesson, we will learn the fundamentals of testing in Angular with Vitest.

RenderMode
Use Prerender for static pages (best performance), Server for dynamic pages with fresh data, and Client for sections that require authentication or browser APIs.
Browser APIs
In SSR, window, document, localStorage, and sessionStorage don't exist. Use isPlatformBrowser() or afterNextRender() to run code that depends on the browser.
// --- app.config.server.ts ---
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRoutesConfig } from '@angular/ssr';
import { serverRoutes } from './app.routes.server';
import { appConfig } from './app.config';

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

export default mergeApplicationConfig(appConfig, serverConfig);

// --- app.routes.server.ts ---
import { RenderMode, ServerRoute } from '@angular/ssr';

export const serverRoutes: ServerRoute[] = [
  // Static pages: pre-rendered at build time
  { path: '', renderMode: RenderMode.Prerender },
  { path: 'blog', renderMode: RenderMode.Prerender },
  { path: 'courses', renderMode: RenderMode.Prerender },

  // Pages with parameters: SSR on the server
  {
    path: 'blog/:slug',
    renderMode: RenderMode.Server,
  },

  // Dashboard: client-only (no SSR)
  { path: 'dashboard/**', renderMode: RenderMode.Client },

  // Catch-all route
  { path: '**', renderMode: RenderMode.Server },
];