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.
// --- 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