On this page
SSR, pre-rendering, and deployment
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/ssrThis generates:
src/app/app.config.server.ts— Server configurationsrc/app/app.routes.server.ts— Routes with render modesserver.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 deployDocker (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
- Configure render modes: Define an
app.routes.server.tsfile with at least 3 routes using all three modes:Prerenderfor the home page,Serverfor dynamic routes, andClientfor the dashboard. - Protect browser APIs: Create a service that uses
localStorage. Wrap it withisPlatformBrowser()so it does not fail during SSR, and add a server-side fallback. - Add SEO meta tags: Use the
TitleandMetaservices inside aneffect()to dynamically set the page title anddescriptionandog:titletags 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 },
];
Sign in to track your progress