Modulo 2 di 12 · Durata stimata: 45 minuti · Lab GitHub: ✓ (coming soon)
In questo modulo vedrai come aggiungere @angular/ssr a un progetto Angular 21 esistente. Esploreremo l'anatomia del file `server.ts`, come funziona l'Express app integrata, e come debuggare i problemi comuni che emergono quando server-side rendering entra in gioco.
Questo modulo è pratico: avrai uno snippet di server.ts funzionante e saprai come avviare il tuo sito in SSR locally.
Angular fornisce uno schematic ufficiale per aggiungere SSR a un progetto esistente:
ng add @angular/ssr
Questo comando:
Output atteso (console):
✔ Packages installed successfully.
✔ New SSR-related files created.
✔ angular.json updated for SSR builds.
Your application is now ready for server-side rendering!
Run 'npm run build' and then 'npm run serve:ssr' to test locally.
Dopo `ng add @angular/ssr`, il tuo `angular.json` avrà tre builder per la stessa applicazione:
"projects": {
"portfolio": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/portfolio/browser",
"index": "src/index.html",
"main": "src/main.ts",
...
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/portfolio/server",
"main": "src/main.server.ts",
...
}
},
"prerender": {
"builder": "@angular-devkit/build-angular:prerender",
"options": {
"routes": ["src/prerender.routes.ts"],
"numProcesses": 4
}
}
}
}
}
Cosa significa:
Flusso di build SSR:
npm run build
└─ build browser (→ dist/portfolio/browser/)
└─ build server (→ dist/portfolio/server/)
└─ copia assets (→ dist/portfolio/browser/assets/)
npm run serve:ssr
└─ avvia Express server (legge dist/portfolio/server/main.js)
└─ per ogni richiesta HTTP:
└─ carica il bundle server Angular
└─ renderizza il componente a HTML
└─ invia HTML + assets al browser
Questo è il cuore di SSR. Ecco una versione commentata basata su quella di federicocalo.dev:
// server.ts — Express app per SSR
import express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
// Ricava __dirname in ES modules
const filename = fileURLToPath(import.meta.url);
const dirname_path = dirname(filename);
// Express app
const app = express();
// Carica il bootstrap Angular lato server
const { AppServerModule } = await import(
join(dirname_path, 'server', 'main.js')
);
// Configura CommonEngine (render SSR)
const commonEngine = new CommonEngine();
// === MIDDLEWARE ===
// 1. Servire static assets (CSS, JS, images)
// Importante: deve andare PRIMA del catch-all route
app.use(
express.static(join(dirname_path, 'browser'), {
maxAge: '1y', // Browser cache 1 anno per immutable assets
etag: false, // Disabilita ETag se vuoi cache aggressivo
})
);
// 2. Security headers (CSP, HSTS, X-Content-Type-Options)
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
);
next();
});
// === ROUTING ===
// 1. Tutte le altre rotte (catch-all) → renderizza con SSR
app.get('*', (req, res) => {
// La BaseHref deve corrispondere al basePath della tua app
const baseUrl = `${req.protocol}://${req.get('host')}/`;
// Renderizza il componente Angular a HTML
commonEngine
.render({
bootstrap: AppServerModule,
providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl }, // URL corrente
],
url: `${baseUrl}${req.url}`, // URL completo per Angular routing
document: template, // HTML template (vedi sotto)
inlineCriticalCss: true, // Inline CSS critico nel
})
.then((html) => {
// HTML renderizzato con successo
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.send(html);
})
.catch((err) => {
// Errore durante rendering
console.error('SSR render error:', err);
res.status(500).send('Server render error');
});
});
// === SERVER START ===
const port = process.env['PORT'] || 4000;
app.listen(port, () => {
console.log(`Angular SSR server running at http://localhost:${port}`);
});
Dettagli importanti:
Locale (development):
npm run serve:ssr
// Avvia Express su :4000
// Legge dist/ (aggiorna su rebuild con --watch)
// Verboso logging
// Nessun caching header
Production (EC2/Heroku):
node dist/portfolio/server/main.js
// Avvia Express su :4000 (o PORT env var)
// Serve da /opt/portfolio/dist/ (path assoluto)
// Minimal logging (solo errori)
// Cache header aggressivo (1 anno asset immutable)
// Resilience4j circuit breaker se chiama API backend
Una volta configurato:
# Build browser + server bundle
npm run build
# Vedi l'output:
# dist/portfolio/browser/ ← CSS, JS, images (client-side)
# dist/portfolio/server/ ← JavaScript server bundle
# Avvia SSR server locale
npm run serve:ssr
# Visita http://localhost:4000
# Ispezione: view-source del HTML — vedrai contenuto renderizzato (non )
Dopo `ng add @angular/ssr`, il tuo `package.json` avrà questi script:
"scripts": {
"ng": "ng",
"start": "ng serve", // CSR localhost:4200
"build": "ng build", // Build browser + server SSR
"serve:ssr": "node dist/portfolio/server/main.js", // SSR localhost:4000
"prerender": "ng run portfolio:prerender" // SSG: genera HTML statico
}
Quando usare ciascun comando:
Nel 2026, Angular 21 supporta il flag zoneless (experimental ma quasi stable):
Con Zone.js (default attualmente):
// src/main.ts
import 'zone.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent);
Zone.js è una libreria che intercetta tutti gli async (setTimeout, Promise, fetch) per triggerare change detection Angular. Pro: automatic, Contro: 90KB di overhead, complexity.
Zoneless (experimental, future default):
// Aggiungi a angular.json
"projects": {
"portfolio": {
"architect": {
"build": {
"options": {
"aot": true,
"optimization": true,
"sourceMap": false,
"ngZone": "zone.js" // ← cambia a "noop"
}
}
}
}
}
Zoneless usa i Signal/onPush change detection per essere reattivo senza Zone.js. Più piccolo, più predicibile. Nel 2026 userai zoneless, ma nel corso facciamo esempi con Zone.js (default still).
Problema: Il tuo componente usa `window.localStorage` o `window.location`. In server Node.js, `window` non esiste.
Soluzione: Usa `isPlatformBrowser()` per eseguire codice solo client:
import { Component, OnInit, inject } from '@angular/core';
import { PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({
selector: 'app-analytics',
standalone: true,
template: `Analytics tracker`,
})
export class AnalyticsComponent implements OnInit {
private platformId = inject(PLATFORM_ID);
ngOnInit() {
// Esegui solo nel browser
if (isPlatformBrowser(this.platformId)) {
const trackingId = window.localStorage.getItem('tracking_id');
console.log('Tracking ID:', trackingId);
}
}
}
Problema: Usi `document.querySelector()` in un service o componente eseguito server-side.
Soluzione: Stessa cosa — wrappa in `isPlatformBrowser()`:
export class DomService {
private platformId = inject(PLATFORM_ID);
getElement(selector: string) {
if (isPlatformBrowser(this.platformId)) {
return document.querySelector(selector);
}
return null;
}
}
Problema: HTML renderizzato server ≠ HTML generato client dopo idratazione. Angular console log: "HYDRATION_MISMATCH_DETECTED".
Causa comune: Visualizzi dati diversi server vs client (ad es., timezone locale, flag browser).
Soluzione: Abilita deferred hydration (modulo 03 parleremo di questo).
Problema: Una richiesta HTTP nel componente non finisce in tempo, server aspetta eternità.
Soluzione: Aggiungi timeout alla HttpClient:
import { timeout } from 'rxjs/operators';
export class DataService {
constructor(private http: HttpClient) {}
getData() {
return this.http.get('/api/data').pipe(
timeout(5000) // 5 secondi timeout
);
}
}
Obbiettivo: Prendi un progetto Angular 21 piccolo (3-5 componenti) e aggiungici SSR.
Passi:
ng new demo-ssr --routing (crea una nuova app)ng add @angular/ssr (aggiungi SSR)npm run build (compila SSR)npm run serve:ssr (testa su :4000)Code repo (coming soon): github.com/fedcal01/angular-ssr-masterclass-labs/tree/main/module-02
Nel modulo 03 parleremo di standalone components e signals server-side. Vedrai come scrivere componenti che funzionano bene sia server che client, usando pattern moderni Angular 21 (signal inputs, effects condizionali, deferred rendering).
→ Vai al modulo 03: Standalone components + signals server-side
Domande? Chiedi nei commenti sotto o su GitHub Discussions.