Déployez Angular Universal SSR sur Vercel : prérendu hybride, vercel.json, edge runtime, hydration et SEO optimisé pour applications Angular.
Pourquoi SSR pour une application Angular ?
Une application Angular classique (CSR — Client-Side Rendering) sert un index.html quasi vide. Le navigateur télécharge ensuite le bundle JavaScript, démarre Angular, exécute les composants et seulement à ce moment le contenu apparaît. Sur connexion mobile 4G médiane, ce processus prend 3 à 5 secondes : le robot Googlebot voit une page blanche, les balises Open Graph sont absentes lors du partage social, et le Largest Contentful Paint (LCP) devient critique pour le SEO.
Le Server-Side Rendering avec Angular Universal résout ce problème en exécutant le rendu sur Node.js (ou un edge runtime) avant d'envoyer le HTML complet au navigateur. Le client reçoit immédiatement un document indexable, puis Angular « hydrate » l'application — c'est-à-dire qu'il associe les composants TypeScript aux nœuds DOM existants sans recharger.
Bénéfices mesurables en production
- SEO : indexation immédiate, balises meta dynamiques visibles, rich snippets opérationnels
- Performance perçue : First Contentful Paint divisé par 3 ou 4 sur mobile
- Partage social : Open Graph et Twitter Cards générés côté serveur
- Accessibilité : contenu disponible sans JavaScript (lecteurs d'écran low-end, navigateurs limités)
- Conversion : 100ms de gain sur le LCP = environ 1% de conversion supplémentaire d'après les études Akamai et Google
Pourquoi déployer sur Vercel précisément ?
Vercel est conçu pour les frameworks SSR modernes. Sa plateforme combine un CDN edge multi-régions, des Serverless Functions Node.js qui exécutent le rendu Angular, un edge runtime V8 isolates pour des cold starts millisecondes, du Preview Deployment automatique sur chaque pull request GitHub, et un build cache distribué qui accélère drastiquement les déploiements répétés.
Activer SSR avec ng add @angular/ssr
Depuis Angular 17, le paquet officiel @angular/ssr remplace l'ancien @nguniversal/express-engine. Une seule commande suffit pour ajouter SSR à un projet existant : tout est généré, aucune réécriture des composants n'est nécessaire.
Prérequis
# Vérifier la version Node (Vercel exige Node 20+ en 2026)
node --version
# v20.18.0 ou supérieur
# Vérifier Angular CLI
ng version
# Angular CLI: 19.x ou 20.x recommandé
# Mise à jour si besoin
npm install -g @angular/cli@latest
Installation du paquet SSR
# Dans la racine du projet Angular existant
ng add @angular/ssr
# La CLI demande :
# ? Would you like to enable Server-Side Rendering (SSR)
# and Static Site Generation (SSG/Prerendering)? Yes
Fichiers générés automatiquement
La commande modifie ou crée plusieurs fichiers : src/app/app.config.server.ts (configuration spécifique serveur), src/main.server.ts (point d'entrée bootstrap serveur), src/server.ts (serveur Express qui sert les requêtes), et met à jour angular.json avec une nouvelle configuration server et un block prerender.
// src/app/app.config.server.ts
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { provideServerRouting } from '@angular/ssr';
import { appConfig } from './app.config';
import { serverRoutes } from './app.routes.server';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
// Active le routage spécifique au serveur (prerender, SSR, ISR)
provideServerRouting(serverRoutes),
],
};
// Merge avec la config client commune (router, http, etc.)
export const config = mergeApplicationConfig(appConfig, serverConfig);
// src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
// Page d'accueil : prerender au build (SSG pur)
{ path: '', renderMode: RenderMode.Prerender },
// Pages statiques : prerender
{ path: 'about', renderMode: RenderMode.Prerender },
{ path: 'pricing', renderMode: RenderMode.Prerender },
// Articles dynamiques : SSR à chaque requête
{ path: 'blog/:slug', renderMode: RenderMode.Server },
// Dashboard utilisateur : pas de SSR (CSR pur, derrière auth)
{ path: 'dashboard/**', renderMode: RenderMode.Client },
// Catch-all : SSR par défaut
{ path: '**', renderMode: RenderMode.Server },
];
Build local — vérifier le SSR
# Build production avec SSR
npm run build
# Démarrer le serveur Node local pour tester
npm run serve:ssr:my-app
# Ouvrir http://localhost:4000 et inspecter la source HTML
# Le contenu doit être présent dans la première réponse,
# pas seulement chargé après JavaScript
curl -s http://localhost:4000 | grep -i "<h1"
# <h1>Bienvenue sur mon site Angular</h1> ← présent immédiatement
curl ne renvoie pas le HTML rendu, vérifiez que vous avez lancé serve:ssr et non start. La commande ng serve ne fait jamais de SSR — c'est le mode dev pur navigateur.
Configuration vercel.json pour Angular
Vercel détecte automatiquement Angular grâce à angular.json, mais pour SSR il est indispensable de fournir un fichier vercel.json à la racine. Ce fichier déclare la build, les fonctions serverless et le routage des requêtes vers le bundle SSR.
vercel.json minimal pour Angular SSR
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"version": 2,
"buildCommand": "npm run build",
"outputDirectory": "dist/my-app",
"installCommand": "npm ci",
"framework": null,
"functions": {
"api/server.js": {
"runtime": "nodejs20.x",
"memory": 1024,
"maxDuration": 30
}
},
"rewrites": [
{
"source": "/(.*)",
"destination": "/api/server"
}
]
}
Adapter Vercel — fichier api/server.js
// api/server.js — handler Vercel qui appelle le serveur Angular SSR
// Ce fichier doit être à la racine du projet, dans /api
import { createNodeRequestHandler } from '@angular/ssr/node';
import bootstrap from '../dist/my-app/server/main.server.mjs';
// Vercel exige un default export compatible Node HTTP
export default createNodeRequestHandler(bootstrap);
angular.json — vérifier les outputs
{
"projects": {
"my-app": {
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/my-app",
"browser": "src/main.ts",
"server": "src/main.server.ts",
"ssr": {
"entry": "src/server.ts"
},
"prerender": {
"discoverRoutes": true,
"routesFile": "src/prerender-routes.txt"
}
}
}
}
}
}
}
Premier déploiement
# Installer la CLI Vercel
npm install -g vercel
# Lier le projet (création d'un projet Vercel)
vercel link
# Déployer en preview (URL temporaire)
vercel
# Déployer en production
vercel --prod
# Sortie attendue
# Inspect: https://vercel.com/account/my-app/abc123 [2s]
# Production: https://my-app.vercel.app [42s]
node_modules, le cache Angular CLI (.angular/cache) et les artefacts intermédiaires. Pour invalider manuellement, utilisez vercel --force ou supprimez le cache dans Settings → General → Build Cache.
Routes prerender et ISR hybride
Le prerender (Static Site Generation) génère le HTML au build. C'est l'option la plus rapide à servir : le CDN Vercel renvoie un fichier statique sans cold start. Pour les routes dynamiques (articles, fiches produit), Angular 19+ supporte l'Incremental Static Regeneration (ISR) via le paramètre RenderMode.Prerender combiné à des route parameters.
Prerender de routes dynamiques avec getPrerenderParams
// src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr';
import { inject } from '@angular/core';
import { ArticleService } from './services/article.service';
export const serverRoutes: ServerRoute[] = [
{
path: 'blog/:slug',
renderMode: RenderMode.Prerender,
// Liste les slugs à prerender au build
async getPrerenderParams() {
const articleService = inject(ArticleService);
const articles = await articleService.fetchAllSlugs();
// Retourne un tableau d'objets { slug: '...' }
return articles.map(a => ({ slug: a.slug }));
},
},
// Fallback : si un nouveau slug arrive après le build → SSR à la demande
{ path: 'blog/**', renderMode: RenderMode.Server },
];
Cache hybride avec ISR — Vercel revalidate
// api/server.js — ajouter des headers de cache CDN
import { createNodeRequestHandler } from '@angular/ssr/node';
import bootstrap from '../dist/my-app/server/main.server.mjs';
const baseHandler = createNodeRequestHandler(bootstrap);
export default async function handler(req, res) {
// Cache CDN Vercel : 1h fresh, 24h stale-while-revalidate
// → contenu servi instantanément depuis l'edge
// → régénéré en arrière-plan si plus vieux que 1h
res.setHeader(
'Cache-Control',
's-maxage=3600, stale-while-revalidate=86400'
);
return baseHandler(req, res);
}
Comportement résultant
| Type de route | Mode Angular | Latence typique | Cas d'usage |
|---|---|---|---|
/, /about |
RenderMode.Prerender |
20–40 ms (CDN) | Pages marketing statiques |
/blog/:slug (connus) |
Prerender + ISR | 30 ms (cache hit) | Articles, fiches produit catalogue |
/search?q=... |
RenderMode.Server |
200–400 ms (cold) | Résultats dynamiques personnalisés |
/dashboard |
RenderMode.Client |
n/a (CSR pur) | App authentifiée, données privées |
Forcer un rebuild Vercel après publication
# Webhook Deploy Hook : créer une URL POST dans Settings → Git
# Puis depuis le CMS / backend après publication d'un article :
curl -X POST \
https://api.vercel.com/v1/integrations/deploy/prj_abc/abc123hook
# → Vercel lance un build complet
# → Le nouveau slug devient prerendered en ~2 min
RenderMode.Server avec stale-while-revalidate. Le compromis : le cold start de la première visite (200–400ms) versus la fraîcheur immédiate du contenu.
Edge Runtime pour Angular SSR
L'Edge Runtime de Vercel exécute votre code dans des V8 isolates répartis sur 30+ régions mondiales. Comparé à une Serverless Function Node.js classique, l'edge offre des cold starts en dizaines de millisecondes (vs 300–600ms pour Node) et une latence réduite grâce à la proximité géographique avec l'utilisateur.
Limites du runtime Edge pour Angular
Le runtime Edge est basé sur une API Web standard (Fetch, Request, Response, Streams) et n'expose pas l'intégralité de Node.js. Avant d'activer Edge sur une route Angular SSR, vérifiez que votre code respecte ces limites :
- Pas de modules Node natifs (
fs,net,child_process) - Pas de dépendances qui les utilisent en transitif (vérifier avec
npm ls fs) - Taille du bundle Edge limitée à 4 MB après compression
- Durée d'exécution limitée à 25 secondes (vs 30s en Node serverless)
- Pas de connexion TCP brute (utiliser
fetchuniquement)
Configurer une route Edge dans vercel.json
{
"version": 2,
"buildCommand": "npm run build",
"outputDirectory": "dist/my-app",
"functions": {
"api/server-node.js": {
"runtime": "nodejs20.x",
"memory": 1024,
"maxDuration": 30
},
"api/server-edge.js": {
"runtime": "edge",
"memory": 128
}
},
"rewrites": [
{ "source": "/blog/(.*)", "destination": "/api/server-edge" },
{ "source": "/(.*)", "destination": "/api/server-node" }
]
}
Handler Edge Angular
// api/server-edge.js — handler edge runtime
// ATTENTION : utiliser uniquement les API Web (pas Node)
export const config = {
runtime: 'edge',
regions: ['cdg1', 'iad1', 'sfo1'], // Paris, Virginie, San Francisco
};
import { renderApplication } from '@angular/platform-server';
import bootstrap from '../dist/my-app/server/main.server.mjs';
export default async function handler(request) {
const url = new URL(request.url);
// Render Angular vers une chaîne HTML
const html = await renderApplication(bootstrap, {
document: '<app-root></app-root>',
url: url.pathname + url.search,
});
return new Response(html, {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 's-maxage=600, stale-while-revalidate=3600',
},
});
}
Vérifier le runtime déployé
# Inspecter les headers de réponse pour confirmer le runtime
curl -I https://my-app.vercel.app/blog/article-1
# HTTP/2 200
# x-vercel-cache: HIT
# x-vercel-id: cdg1::abc123-1715000000000-abc
# server: Vercel
#
# La région cdg1 = Paris (edge)
# Si vous voyez iad1::lambda::... → c'est une lambda Node, pas edge
Variables d'environnement Vercel
Vercel sépare les variables d'environnement en 3 scopes : Development (local via vercel dev), Preview (chaque pull request) et Production (branche main déployée sur le domaine principal). Cette séparation est essentielle pour Angular SSR : la clé API utilisée par votre HttpClient doit changer selon l'environnement.
Définir une variable depuis la CLI
# Ajouter une variable production
vercel env add API_BASE_URL production
# ? What's the value of API_BASE_URL? https://api.example.com
# Idem pour preview et development
vercel env add API_BASE_URL preview
vercel env add API_BASE_URL development
# Lister toutes les variables
vercel env ls
# Récupérer les variables localement (génère .env.local)
vercel env pull
Exposer la variable côté Angular
Angular ne lit pas process.env au runtime côté navigateur — il faut injecter les valeurs au moment du build dans environment.ts. Vercel facilite ce pattern grâce à un script de génération exécuté avant ng build.
// scripts/generate-env.js — exécuté avant le build
// Lit les variables Vercel et écrit src/environments/environment.prod.ts
import { writeFileSync } from 'fs';
import { join } from 'path';
const targetPath = join('src', 'environments', 'environment.prod.ts');
const envContent = `// Fichier généré automatiquement — NE PAS éditer
export const environment = {
production: true,
apiBaseUrl: '${process.env.API_BASE_URL || ''}',
sentryDsn: '${process.env.SENTRY_DSN || ''}',
// Variables Vercel système (toujours disponibles)
vercelEnv: '${process.env.VERCEL_ENV || 'development'}',
vercelRegion: '${process.env.VERCEL_REGION || 'local'}',
gitCommitSha: '${process.env.VERCEL_GIT_COMMIT_SHA || ''}',
};
`;
writeFileSync(targetPath, envContent);
console.log('environment.prod.ts généré pour', process.env.VERCEL_ENV);
// package.json — chaîner generate-env avant le build
{
"scripts": {
"build": "node scripts/generate-env.js && ng build",
"vercel-build": "node scripts/generate-env.js && ng build --configuration=production"
}
}
Variables système Vercel utiles
| Variable | Valeurs possibles | Cas d'usage |
|---|---|---|
VERCEL_ENV |
development | preview | production | Désactiver analytics en preview |
VERCEL_URL |
my-app-abc123.vercel.app | URL canonique pour Open Graph |
VERCEL_GIT_COMMIT_SHA |
abc123def456 | Tag de version Sentry / DataDog |
VERCEL_REGION |
cdg1 | iad1 | sfo1... | Routing data center le plus proche |
Preview Deployments — un environnement par PR
# Workflow GitHub → Vercel automatique
# 1. Vous poussez une branche feat/new-header
# 2. Vercel détecte la PR et déploie automatiquement
# 3. URL générée : https://my-app-feat-new-header-team.vercel.app
# 4. Bot GitHub commente la PR avec l'URL preview
# 5. Lors du merge, Vercel promote en production
# Pour récupérer l'URL preview en script CI :
vercel ls my-app --token=$VERCEL_TOKEN | head -2
localhost:4000.
Performance et Lighthouse avant/après
La promesse du SSR Angular sur Vercel est concrète : voici les mesures Lighthouse réalisées sur une application de blog avec ~150 articles, mesurées en mode incognito sur Chrome 130 et connexion 4G simulée (slow 4G, 1.6 Mbps download, 750ms RTT).
Comparatif Lighthouse — page article
| Métrique | CSR (avant) | SSR Vercel (après) | Gain |
|---|---|---|---|
| Performance Score | 62 / 100 | 96 / 100 | +34 pts |
| First Contentful Paint | 3.2 s | 0.7 s | −78% |
| Largest Contentful Paint | 4.1 s | 1.1 s | −73% |
| Time to Interactive | 5.8 s | 2.4 s | −59% |
| Total Blocking Time | 620 ms | 180 ms | −71% |
| Cumulative Layout Shift | 0.12 | 0.02 | −83% |
| SEO Score | 91 / 100 | 100 / 100 | +9 pts |
Optimiser l'hydration avec @defer
Angular 17+ introduit le bloc @defer qui charge un composant lazy uniquement à la demande (au scroll, au hover, au click). Combiné au SSR, ce mécanisme permet de réduire le bundle critique tout en gardant le HTML serveur complet pour les crawlers.
// article-detail.component.ts — template
@Component({
template: `
<article>
<h1>{{ article().title }}</h1>
<p class="lead">{{ article().excerpt }}</p>
<!-- Contenu critique : présent dans le HTML SSR -->
<div [innerHTML]="article().contentHtml"></div>
<!-- Section commentaires : chargée seulement au scroll -->
@defer (on viewport) {
<app-comments [articleId]="article().id" />
} @placeholder {
<p class="text-muted">Chargement des commentaires...</p>
} @loading (after 100ms) {
<div class="spinner-border" role="status"></div>
}
<!-- Articles liés : chargés au hover de la zone -->
@defer (on hover) {
<app-related-articles [tag]="article().tag" />
}
</article>
`,
})
export class ArticleDetailComponent { /* ... */ }
Vercel Speed Insights — monitoring continu
# Installer le client Speed Insights
npm install @vercel/speed-insights
# Activer dans app.config.ts (côté client)
# src/app/app.config.ts
import { ApplicationConfig } from '@angular/core';
import { injectSpeedInsights } from '@vercel/speed-insights';
export const appConfig: ApplicationConfig = {
providers: [
{
provide: 'SPEED_INSIGHTS_INIT',
useFactory: () => {
if (typeof window !== 'undefined') {
injectSpeedInsights({
framework: 'angular',
sampleRate: 1, // 100% en dev, baisser en prod fort trafic
});
}
return null;
},
},
],
};
Vercel vs Netlify vs Cloudflare Pages
Trois plateformes dominent l'hébergement de frameworks SSR JavaScript en 2026. Voici un comparatif factuel pour Angular SSR spécifiquement, basé sur des déploiements réels d'une même application Angular 19.
| Critère | Vercel | Netlify | Cloudflare Pages |
|---|---|---|---|
| Plan gratuit (compute) | 100 GB-h / mois | 125 000 invocations | 100 000 req/jour (Workers) |
| Plan payant entrée | 20$/user/mois | 19$/user/mois | 5$/mois flat (Workers Paid) |
| Bandwidth gratuit | 100 GB | 100 GB | Illimité (CDN) |
| Edge runtime cold start | ~10 ms (V8 isolates) | ~50 ms (Edge Functions) | ~5 ms (Workers V8) |
| Node serverless cold start | 200–500 ms | 300–700 ms | n/a (Workers only) |
| Adapter Angular officiel | Oui (auto-detect) | Oui (@netlify/angular-runtime) | Manuel (wrangler.toml) |
| Preview deploy par PR | Auto (GitHub/GitLab) | Auto | Auto (depuis 2024) |
| ISR / cache CDN | stale-while-revalidate natif | On-demand revalidation | KV / Cache API |
| Régions edge | 30+ régions | ~13 régions | 300+ POPs |
| Documentation Angular | Excellente | Bonne | Limitée (community) |
Recommandation par profil
- Startup / projet perso → Vercel Hobby : DX imbattable, doc Angular parfaite, plan gratuit généreux
- Équipe produit (3–10 dev) → Vercel Pro : analytics, observability, RBAC, support prio
- Trafic massif & budget serré → Cloudflare Pages : bandwidth illimité, Workers à 5$/mois
- Écosystème Jamstack étendu (form, identity) → Netlify : excellente intégration de plugins (forms, identity, large media)
Conclusion
Déployer une application Angular SSR sur Vercel n'a jamais été aussi accessible. La commande ng add @angular/ssr génère toute l'infrastructure serveur, un vercel.json de quelques lignes pilote build et runtime, et le hybrid prerender/SSR/edge permet d'optimiser chaque route selon son profil de trafic. À la clé : un score Lighthouse au-dessus de 95, un FCP sous 1 seconde et une indexation Google immédiate.
Au-delà de la performance brute, Vercel apporte un environnement de développement industriel : Preview Deployments par pull request, Speed Insights pour le monitoring real-user, et une intégration GitHub native qui supprime les frictions du cycle CI/CD. Pour la majorité des projets Angular en 2026, c'est la voie la plus courte vers une production fluide.
- Angular 17+ avec
@angular/ssrinstallé viang add vercel.jsonavecfunctionsetrewritesdéfinisapp.routes.server.tsavec stratégie par route (Prerender / Server / Client)- Variables d'env séparées Development / Preview / Production
- Cache CDN configuré (
stale-while-revalidate) pour les routes ISR - Edge runtime testé sur les routes courtes critiques (latence < 50ms)
- Vercel Speed Insights activé pour mesurer les Core Web Vitals RUM
- Webhook Deploy Hook configuré si publication via CMS externe
- Lighthouse score ≥ 90 vérifié sur 3 routes critiques (home, article, search)
@deferutilisé sur les sections lourdes (commentaires, articles liés)- Open Graph et Twitter Cards générés côté serveur via
Metaservice - Sentry ou DataDog connecté avec
VERCEL_GIT_COMMIT_SHAcomme release tag