SEO per Angular
Come rendere le app Angular scansionabili e indicizzabili — @angular/ssr rispetto a prerendering e rendering ibrido, hydration, servizi Title/Meta, routing History API e test di ciò che Googlebot renderizza davvero.
Lingue
La SEO di Angular si riduce a una decisione: servi HTML reale invece di un guscio renderizzato dal client. Angular moderno (v17+) integra l’SSR nella CLI come @angular/ssr: fai il prerendering delle route statiche, esegui il rendering server di quelle dinamiche e fai la hydration affinché l’HTML non venga scartato. Poi cura le basi: title e description unici tramite i servizi Angular Title e Meta, routing HTML5 History (mai URL con hash), veri link <a href> e verifica di ciò che Googlebot renderizza tramite URL Inspection. Il rendering dinamico è una soluzione tampone, non una strategia; AngularJS è un framework diverso.
TL;DR — Angular costruisce la pagina nel browser con JavaScript per impostazione predefinita, quindi l’HTML grezzo che un motore di ricerca vede per primo è quasi vuoto. La soluzione è inviare HTML reale e completo, usando il rendering lato server integrato di Angular (
@angular/ssr) oppure il prerendering. Poi cura le basi: un title e una description unici per ogni pagina, URL puliti (senza#) e veri link<a href>.
Il problema in una frase
Un’app Angular predefinita invia al browser un piccolo guscio HTML — in pratica un <div> vuoto — più un grande bundle JavaScript. Il browser esegue quel JavaScript e poi la pagina si riempie di contenuti. Si chiama rendering lato client (CSR).
Il problema è che, quando un motore di ricerca scarica per la prima volta quell’URL, vede il guscio vuoto. Google può eseguire il JavaScript per vedere il contenuto reale, ma lo fa più tardi, in un passaggio separato, e non sempre in modo affidabile. Evidence for this claim Google can render JavaScript but processes rendering as a stage after crawling. Scope: Google Search rendering; other crawler behavior is not covered by this record. Confidence: high · Verified: Google: JavaScript SEO basics Gli altri crawler — Bing e i bot dietro le anteprime social — spesso non riescono affatto a eseguire JavaScript. Quindi non vedono nulla.
La soluzione: inviare HTML completo
Invece di lasciare che siano il browser o il crawler a costruire la pagina, costruiscila in anticipo o su un server e invia l’HTML completo. Angular offre due modalità principali:
- Rendering lato server (SSR) — un server esegue Angular per ogni richiesta e restituisce la pagina completa. È adatto ai contenuti che cambiano spesso. Evidence for this claim Angular supports server-side rendering and build-time prerendering through its SSR tooling. Scope: Current Angular SSR and prerender features. Confidence: high · Verified: Angular: Server-side and hybrid rendering
- Prerendering — Angular costruisce file HTML statici per le tue pagine al momento della build, quindi non serve un server. È l’opzione più veloce, ottima per articoli e pagine di marketing.
Angular moderno (versione 17 e successive) integra entrambe le modalità nel toolkit. Le aggiungi con un solo comando: ng add @angular/ssr. Potresti aver sentito il vecchio nome Angular Universal: era la stessa idea come add-on separato. Angular l’ha integrata nel core nella v17 e l’ha rinominata.
Le altre basi
- Dai a ogni pagina un title e una description unici. Angular non lo fa per te: li imposti nel codice usando i servizi integrati
TitleeMeta. Senza questa configurazione, ogni pagina condivide lo stesso title. - Usa URL puliti, non URL con hash. Il routing predefinito di Angular crea URL ordinati come
/products/shoes. Evita il vecchio stile con hash (/#/products): i motori di ricerca non gestiscono in modo affidabile la parte dopo#. - Usa link reali. La navigazione deve usare veri link
<a href>, non pulsanti o gestori di eventi, altrimenti Google non può seguirli.
L’errore più comune
«Google non può indicizzare Angular» è un mito: può eseguire il rendering di JavaScript. Ma è più lento e meno affidabile che inviare direttamente HTML reale, e i crawler diversi da Google non possono farlo affatto. Quindi, per tutto ciò che vuoi far trovare, esegui il rendering sul server o fai il prerendering.
Vuoi la versione più approfondita — rendering ibrido per route, hydration, servizi SEO con codice e test di ciò che Google vede davvero? Passa alla scheda Advanced.
Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid renderingTL;DR — La SEO di Angular è una decisione architetturale con molti nomi: inserisci HTML reale nella risposta invece di un guscio renderizzato dal client. Angular moderno (v17+) integra l’SSR nella CLI come
@angular/ssr(il successore rinominato e integrato di Angular Universal). Fai il prerender delle route statiche, esegui il rendering server delle route dinamiche, combina le due modalità con il rendering ibrido e fai l’hydration per riutilizzare l’HTML del server invece di ricostruirlo. Poi cura le basi: title e description unici tramite i serviziTitle/Meta(oTitleStrategydel router), routing HTML5 History — maiHashLocationStrategy— veri link<a href>, JSON-LD iniettato in sicurezza e verifica tramite URL Inspection. Il rendering dinamico è una soluzione tampone riconosciuta da Google, non una strategia.
Il problema predefinito: rendering lato client
Una build Angular standard distribuisce un index.html il cui body è essenzialmente <app-root></app-root> più i tag script. Senza eseguire JavaScript, un crawler non vede heading, testo o link: il contenuto viene assemblato nel browser dopo il caricamento del bundle. È lo stesso problema di fondo descritto in JavaScript SEO: il web si è allontanato dal semplice HTML e il CSR è l’estremo più rischioso di quello spettro.
(I dettagli di implementazione di questa guida — API delle modalità di rendering, trigger di hydration ed event replay — riflettono Angular v22. Le funzionalità specifiche delle versioni sono datate: il cambio di nome dell’SSR nella v17, l’event replay nella v18 e l’hydration incrementale nelle v19–v20.)
Il CSR crea tre problemi SEO distinti:
- Indicizzazione ritardata e meno affidabile. Google elabora le app JavaScript in tre fasi — “Crawling, Rendering, and Indexing” — e il rendering viene rinviato a una coda. Il tuo contenuto non esiste per l’indicizzazione finché quel rendering non viene eseguito.
- Core Web Vitals peggiori. L’LCP ne risente perché il browser deve scaricare ed eseguire un bundle prima di dipingere contenuto significativo.
- I crawler diversi da Google vedono il guscio. Bingbot è molto meno coerente nel rendering JavaScript e i bot per le anteprime social/link in genere non fanno rendering: i tag Open Graph e il contenuto iniettati lato client quindi non li raggiungono.
Come Googlebot gestisce davvero un’app Angular
Googlebot è evergreen: esegue il rendering con una versione attuale del motore V8 di Chrome e si aggiorna insieme alle release di Chrome. Evidence for this claim Googlebot uses an evergreen version of Chromium for rendering. Scope: Google Search rendering engine; this does not remove application-level rendering risks. Confidence: high · Verified: Google: Evergreen Googlebot Quindi può eseguire Angular. Ma il comportamento ha limiti precisi attorno ai quali conviene progettare:
- Il rendering è accodato, non immediato. Google documenta fasi distinte di crawling, rendering e indicizzazione e non pubblica una tempistica fissa per quanto il rendering impieghi a recuperare il ritardo sul crawling. La definizione sintetica usata nel settore è «due ondate»: prima si indicizza l’HTML grezzo (un guscio vuoto per Angular CSR), poi il DOM renderizzato, quando viene eseguito quel rendering. SSR/prerendering elimina l’attesa: l’HTML è completo al primo fetch, quindi per quel contenuto non c’è un passaggio di rendering separato da aspettare.
- Il renderer è stateless. Non conserva cookie,
localStorageosessionStoragetra i caricamenti e rifiuta le richieste di autorizzazione. Non subordinare il contenuto allo stato del client. - Non fa clic né scroll e usa una viewport molto alta. Il contenuto dietro un’interazione non verrà visto.
- Le risorse vengono messe in cache in modo aggressivo: usa nomi di file con fingerprint del contenuto (il
main.<hash>.jspredefinito di Angular) per evitare che i bundle aggiornati vengano serviti dalla cache ormai obsoleti.
@angular/ssr: cos’è e la storia del nome
Il rendering lato server esegue Angular su un server Node per ogni richiesta e restituisce HTML completamente renderizzato, che il browser poi idrata (collega gli event listener al DOM esistente invece di renderizzarlo di nuovo). Ogni crawler — Googlebot, Bingbot e i bot social — riceve HTML completo alla prima richiesta, senza bisogno di una seconda ondata.
Il nome crea confusione, quindi sii preciso: “Angular Universal” era la soluzione SSR storica, distribuita come pacchetto esterno (@nguniversal/express-engine). Con Angular v17 (novembre 2023), l’SSR è stato integrato direttamente nella CLI di Angular e nell’Application Builder e rinominato @angular/ssr; il repository Angular Universal è ora in modalità manutenzione. Evidence for this claim Modern Angular documents @angular/ssr as its server-side and hybrid rendering package. Scope: Current Angular documentation; historical Angular Universal details are not required for this claim. Confidence: high · Verified: Angular: Server-side and hybrid rendering La configurazione oggi è una sola riga:
# New project with SSR enabled
ng new my-app --ssr
# Add SSR to an existing project
ng add @angular/ssrQuesto sostituisce il vecchio ng add @nguniversal/express-engine. La stessa idea, con strumenti integrati di prima classe.
Prerendering (generazione statica / SSG)
Il prerendering genera HTML statico per le route al momento della build, quindi non viene eseguito alcun server al momento della richiesta: puoi distribuire l’app su una CDN. Offre il TTFB/FCP/LCP più veloce e il costo operativo più basso. Nella v17+ lo configuri per route tramite un file delle route server (app.routes.server.ts) usando RenderMode.Prerender, mentre outputMode: 'static' produce un’app completamente statica.
Il vincolo è che i dati devono essere disponibili al momento della build, non ci sono contenuti per utente e i siti molto grandi implicano build lente. È ideale per pagine di marketing, documentazione e articoli: proprio i contenuti che hanno più bisogno di posizionarsi e di essere citati.
Rendering ibrido — scegli una modalità per ogni route
Il grande passo avanti della v17+ è che la modalità di rendering è una decisione per route, configurata in app.routes.server.ts:
RenderMode.Prerender— route statiche (home, about, articoli).RenderMode.Server— route dinamiche, per richiesta (risultati di ricerca, dashboard con dati aggiornati).RenderMode.Client— route solo interne che non vuoi indicizzare (interfacce amministrative, schermate riservate agli utenti autenticati). Qui il CSR va bene.
Questa è la regola decisionale nella pratica: fai il prerendering di ciò che è statico, esegui il rendering server di ciò che deve essere aggiornato e ricorri al rendering client solo per ciò che non dovrebbe entrare nell’indice.
Hydration — e il problema che rovina il CLS
L’SSR ingenuo ha un difetto: il server invia HTML, poi il browser lo scarta e fa il rendering da zero, causando un flash e lavoro sprecato. La hydration risolve il problema: il browser ripristina l’app renderizzata dal server e riutilizza il DOM corrispondente invece di distruggerlo e ricrearlo, collegando solo l’interattività. Abilitala in app.config.ts:
provideClientHydration()Prerequisito: la hydration è un supporto lato client dell’SSR, non un interruttore autonomo. provideClientHydration() fa qualcosa solo su una route già renderizzata lato server (o prerenderizzata): non può trasformare il guscio di una route RenderMode.Client in HTML server. Prima abilita SSR/prerendering per la route.
Due capacità più recenti sono importanti per l’esperienza utente adiacente alla SEO — nessuna delle due modifica l’indicizzazione nella Ricerca:
- Event replay (v18+): cattura le interazioni supportate che avvengono prima del completamento della hydration e le riproduce una volta terminata. Riduce i clic persi per i tipi di interazione supportati; è una funzionalità UX/interattività, non SEO, e non modifica ciò che Google indicizza.
- Hydration incrementale (anteprima per sviluppatori nella v19, stabile nella v20): dipende insieme da SSR, hydration, viste differibili ed event replay; non è una funzionalità autonoma. È alimentata dai blocchi
@defercon trigger di hydration che controllano quali confini restano disidratati nel rendering iniziale; un confinehydrate neverresta disidratato al primo caricamento, ma non è necessariamente escluso dal caricamento delle dipendenze nei successivi rendering client (per esempio dopo un cambio di route). L’effetto rilevante per la SEO è indiretto: meno JavaScript idratato all’inizio può aiutare l’LCP, ma la configurazione dei trigger è un dettaglio di rendering, non un meccanismo di tempistica del crawler.
Il problema critico: inserire direttamente @if (isPlatformBrowser(...)) in un template fa sì che server e client renderizzino markup diverso — un mismatch di hydration — che produce layout shift e danneggia il CLS. Per il lavoro solo browser usa afterNextRender() invece di ramificare il template in base alla piattaforma.
Title e meta tag — i servizi integrati di Angular
Angular non può fare il binding diretto al testo dell’elemento <title>, quindi gestisci i tag head tramite due servizi di @angular/platform-browser:
Title—setTitle()/getTitle().Meta—addTag(),addTags(),updateTag(),getTag(),removeTag(), con selettori comename='description'oproperty='og:title'.
Per le basi non ti serve una libreria di terze parti. Un servizio SEO condiviso è il pattern più pulito:
@Injectable({ providedIn: 'root' })
export class SeoService {
private title = inject(Title);
private meta = inject(Meta);
updatePage(title: string, description: string) {
this.title.setTitle(title);
this.meta.updateTag({ name: 'description', content: description });
this.meta.updateTag({ property: 'og:title', content: title });
}
}Per i title, in particolare, TitleStrategy del router (Angular v14+) ti permette di impostare un title direttamente nella configurazione della route, così il title della pagina si aggiorna automaticamente durante la navigazione: non scrivere document.title = ... a mano; usa il servizio Title affinché funzioni correttamente sotto SSR.
Struttura degli URL
- Usa il routing predefinito HTML5 History API (
PathLocationStrategy): URL puliti come/products/shoes. Richiede<base href="/">inindex.html. - Non usare mai
HashLocationStrategy/useHash: trueper i contenuti pubblici. Gli identificatori di frammento dopo#vengono rimossi prima della richiesta HTTP, quindi il server non li vede e Googlebot non riesce a risolvere in modo affidabile#/products: l’intero sito può ridursi a un solo URL. - Usa veri link
<a href>per la navigazione, anche verso route caricate lazy. Il lazy loading va bene per le prestazioni, ma i link verso quelle route devono comunque essere anchor scansionabili, non gestori di clic.
Dati strutturati (JSON-LD)
Google supporta l’iniezione di JSON-LD con JavaScript. Il pattern Angular robusto consiste nel creare uno <script type="application/ld+json"> e aggiungerlo a document.head, usando il token di injection DOCUMENT di Angular invece di toccare document globalmente (cosa che si rompe sul server). Mantieni tutto lo schema in un unico posto — non dividerlo tra HTML statico e DOM renderizzato — e convalidalo con Rich Results Test e URL Inspection.
Rendering dinamico — una soluzione tampone legacy, non un piano
Il rendering dinamico serve una versione prerenderizzata ai bot (tramite Puppeteer, Rendertron o prerender.io) e la SPA completa agli utenti. Google è esplicito: “dynamic rendering is a workaround and not a long-term solution,” (traduzione) «il rendering dinamico è una soluzione tampone e non una soluzione a lungo termine» e ci sono “better solutions than dynamic rendering” (traduzione) «soluzioni migliori del rendering dinamico», cioè rendering lato server, rendering statico o hydration. Non è automaticamente cloaking finché servi contenuti sostanzialmente simili, ma aggiunge un server di rendering, rischia il disallineamento dei contenuti e non fa nulla per i Core Web Vitals degli utenti reali. Per una nuova build Angular, scegli @angular/ssr o il prerendering.
Errori SEO comuni in Angular
- Routing con hash (URL
#): l’intero sito sembra un solo URL. - Nessuna chiamata a
Title/Meta: ogni pagina condivide lo stesso title e la stessa description. - Nessun SSR/prerendering: il contenuto esiste solo dopo l’ondata di rendering differita.
- Blocco di
.js/.cssinrobots.txt: Google non può eseguire il rendering e indicizza un guscio vuoto. - Restituzione di
200per una pagina non trovata: è un soft 404; restituisci un vero404oppure aggiunginoindex. document.title = ...invece del servizioTitle.isPlatformBrowser()dentro@ifnel template: mismatch di hydration → CLS.- Accesso a
window/localStorage/documentnel codice eseguito sul server: crash SSR. - Suddivisione dello schema tra HTML grezzo e DOM renderizzato.
- Test in sviluppo locale invece che con URL Inspection: basarsi su supposizioni, non su Googlebot.
Testare Angular per la SEO
- URL Inspection (Search Console) è la fonte di verità: la vista della pagina scansionata mostra l’HTML renderizzato — il DOM dopo che Googlebot ha eseguito JavaScript, cioè ciò che viene indicizzato — oltre ai messaggi della console JS e alle risorse bloccate. Esegui il Live Test per un rendering su richiesta.
- Fai
curldell’URL per vedere l’HTML grezzo, prima di JavaScript: un guscio vuoto significa CSR senza SSR. - Rich Results Test convalida i dati strutturati.
- Ahrefs Site Audit (con rendering JS attivato) e Screaming Frog (in modalità JS) confrontano DOM grezzo e renderizzato su larga scala.
- Lighthouse / PageSpeed Insights misurano l’impatto sui Core Web Vitals della scelta di rendering.
Angular non è cattivo per la SEO: è semplicemente diverso. Inserisci HTML reale nella risposta, gestisci i tag head, mantieni URL e link scansionabili e lascia che siano gli strumenti di Google a decidere cosa è renderizzato. Questo tema si affianca a JavaScript SEO e alle domande sul rendering dei CMS headless in questo cluster: la lezione di fondo è la stessa in tutti i casi, perché la modalità di rendering decide quasi tutto.
Riepilogo AI
Una sintesi della versione Advanced:
- Per impostazione predefinita Angular usa il rendering lato client (CSR): un guscio HTML quasi vuoto. Questo significa indicizzazione ritardata e inaffidabile, LCP peggiore e crawler diversi da Google (Bing e social) che non vedono nulla.
- Googlebot è evergreen ed esegue il rendering JS, ma il rendering è accodato (le «due ondate»: prima HTML grezzo, poi DOM renderizzato, senza tempistica fissa), stateless (niente cookie o storage), non fa clic né scroll e mette aggressivamente in cache le risorse. SSR/prerendering elimina l’attesa: l’HTML è completo al primo fetch, senza un passaggio di rendering separato da attendere.
@angular/ssrè la soluzione moderna: SSR è integrato nella CLI Angular dalla v17 e sostituisce Angular Universal (@nguniversal/express-engine), mantenuto esternamente. Configurazione:ng new --ssroppureng add @angular/ssr.- Il prerendering (HTML statico al momento della build) è il più veloce e distribuibile su CDN: ideale per contenuti statici di marketing, articoli e documentazione; limitato ai dati disponibili durante la build.
- Il rendering ibrido imposta la modalità per route in
app.routes.server.ts:RenderMode.Prerender(statica),RenderMode.Server(dinamica),RenderMode.Client(pagine interne non indicizzate). - La hydration (
provideClientHydration()) riutilizza l’HTML del server su una route già SSR/prerenderizzata: non crea HTML server per il CSR. Event replay (v18+) cattura le interazioni supportate prima della hydration e le riproduce dopo; hydration incrementale (anteprima v19 / stabile v20) dipende da SSR + hydration + viste differibili + event replay e usa i trigger di hydration@deferper controllare quali confini restano disidratati al caricamento iniziale (hydrate nevernon blocca i caricamenti client successivi). Nessuna delle due modifica l’indicizzazione nella Ricerca. EvitaisPlatformBrowser()dentro@ifnel template: causa un mismatch di hydration → CLS; usaafterNextRender(). - Usa i servizi integrati
TitleeMeta(non serve una libreria di terze parti);TitleStrategydel router (v14+) imposta automaticamente i title per route. - Usa il routing HTML5 History, mai URL con hash (
#): la navigazione deve usare anchor reali<a href>. JSON-LD può essere iniettato tramite il tokenDOCUMENT. - Il rendering dinamico è una soluzione tampone, non una strategia: Google raccomanda invece SSR/rendering statico/hydration.
- Testa con URL Inspection (HTML renderizzato),
curl(HTML grezzo), Rich Results Test e un crawler con rendering JS. AngularJS ≠ Angular: i vecchi consigli su AngularJS non si applicano.
Documentazione ufficiale
Documentazione di prima mano da Google e Angular.
- Comprendere le basi della SEO JavaScript — le fasi crawl → render → index, i link
<a href>scansionabili, gli avvisi sul routing con frammenti, gli errori soft di pagina non trovata e i dati strutturati iniettati con JS. - Dynamic Rendering (workaround) — perché è una soluzione tampone, la sfumatura sul cloaking e le alternative SSR/static/hydration.
- Rendering sul web (web.dev — Addy Osmani e Jason Miller) — definizioni canoniche di SSR, CSR, rendering statico e hydration e relativi compromessi prestazionali.
- URL Inspection Tool — come vedere l’HTML renderizzato che Google indicizza davvero, oltre ai messaggi della console JS.
Angular
- Angular — Rendering lato server e ibrido (SSR) — la guida ufficiale a
@angular/ssr, alle route server e aRenderMode. - Angular —
Titleservice —setTitle()/getTitle(). - Angular —
Metaservice —addTag(),updateTag()e selettori. - Angular — Router reference —
PathLocationStrategyrispetto al routing con hash,TitleStrategy. - Introducing Angular v17 (Angular team blog) — l’SSR diventa una funzionalità CLI di prima classe.
- Angular Universal (maintenance mode) — il pacchetto storico, ora sostituito da
@angular/ssr.
Citazioni dalla fonte
Dichiarazioni pubbliche di Google e miei testi. Ogni link a un motore di ricerca porta direttamente al passaggio citato nella pagina sorgente.
Google — come vengono elaborate le app JavaScript
- “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” (traduzione) «Google elabora le app web JavaScript in tre fasi principali: 1. Crawling 2. Rendering 3. Indicizzazione.» — Documentazione di Google Search Central. Jump to quote
- “Google can only discover your links if they are <a> HTML elements with an href attribute.” (traduzione) «Google può scoprire i tuoi link solo se sono elementi HTML <a> con un attributo href.» — Documentazione di Google Search Central. — Jump to quote
Google — il rendering dinamico è una soluzione tampone
- “Dynamic rendering was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” (traduzione) «Il rendering dinamico era una soluzione tampone e non una soluzione a lungo termine per i problemi dei contenuti generati da JavaScript nei motori di ricerca.» — Documentazione di Google Search Central. — Jump to quote
web.dev — preferire SSR / rendering statico (Addy Osmani & Jason Miller)
- “Rendering an app on the server to send HTML, rather than JavaScript, to the client.” (traduzione) «Eseguire il rendering di un’app sul server per inviare HTML al client, invece di JavaScript.» — la definizione del rendering lato server. — Jump to quote
Patrick Stox (mio testo — JavaScript SEO: A Definitive Guide)
- “JavaScript is not bad for SEO, and it’s not evil. It’s just different from what many SEOs are used to.” (traduzione) «JavaScript non è dannoso per la SEO e non è malvagio. È semplicemente diverso da ciò a cui molti professionisti SEO sono abituati.»
- “Any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.” (traduzione) «Qualsiasi configurazione di SSR, rendering statico e prerendering andrà bene per i motori di ricerca.»
Checklist SEO per Angular
Un controllo rapido per confermare che un’app Angular sia scansionabile e indicizzabile:
- Il contenuto pubblico viene servito come HTML reale tramite
@angular/ssr(SSR) o prerendering, non come CSR completo. - La modalità di rendering è impostata per route in
app.routes.server.ts: fai il prerendering delle route statiche, il rendering server di quelle dinamiche e il rendering client solo delle pagine interne non indicizzate. - La hydration è abilitata (
provideClientHydration()) e nessun template usaisPlatformBrowser()dentro@if(usa inveceafterNextRender()). - Ogni pagina imposta un title unico (tramite il servizio
TitleoTitleStrategydel router) e una description unica (tramite il servizioMeta). - I tag Open Graph / Twitter Card sono impostati con il servizio
Metaper le anteprime social. - Il routing usa l’HTML5 History API (predefinito) con
<base href="/">— nonHashLocationStrategy/useHash: true. - Tutta la navigazione usa veri anchor
<a href>, compresi i link verso route caricate lazy. -
robots.txtnon blocca le risorse.jso.css. - Le pagine client-side non trovate restituiscono un vero
404o hannonoindex(nessun soft 404). - Nessun accesso a
window/localStorage/documentnel codice eseguito sul server (usa il tokenDOCUMENT/ i guard della piattaforma). - Il JSON-LD è in un unico posto e supera il Rich Results Test.
- Hai verificato l’HTML renderizzato in URL Inspection, non soltanto nello sviluppo locale.
Modelli mentali
1. Inserisci HTML reale nella risposta. Quasi ogni problema SEO di Angular si riduce a una domanda: il crawler riceve HTML completo al primo fetch o un guscio che deve renderizzare? SSR e prerendering rispondono «sì». Il CSR risponde «prima o poi, forse». Inizia ogni audit da qui.
2. La regola decisionale del rendering per route.
- Contenuto statico (home, about, articoli, documentazione) →
RenderMode.Prerender. - Contenuto dinamico che deve essere aggiornato (ricerca, dati live) →
RenderMode.Server. - Pagine interne/autenticate che non vuoi indicizzare →
RenderMode.Clientva bene.
3. «Angular Universal» e «@angular/ssr» sono la stessa idea, in epoche diverse.
Universal era il pacchetto esterno; la v17 ha assorbito l’SSR nella CLI e l’ha rinominato. Se usi Angular moderno, vuoi @angular/ssr: il repository Universal è in modalità manutenzione.
4. Idrata, non fare di nuovo il rendering.
L’SSR ingenuo invia HTML e poi lo scarta. provideClientHydration() lo riutilizza, ma solo su una route già SSR/prerenderizzata: non crea HTML server per una route CSR. Event replay e hydration incrementale (@defer) riducono il JavaScript iniziale. Non ramificare mai un template con isPlatformBrowser(): è così che ottieni un mismatch di hydration e un impatto sul CLS.
5. I tag head sono compito tuo, non di Angular.
Qui non c’è Yoast. Imposta deliberatamente title e description con i servizi Title/Meta (o TitleStrategy), per ogni pagina. «Tutte le pagine condividono un title» è il fallimento predefinito, non sfortuna.
6. Progetta per un bot stateless su URL puliti.
Routing History API, veri link <a href>, nessuna dipendenza da cookie o storage, nessun contenuto dietro un clic. Poi lascia che sia URL Inspection — non il tuo portatile — a dirti cosa è stato renderizzato.
SEO per Angular — cheat sheet
Modalità di rendering
| Modalità | Dove viene costruito l’HTML | SEO | Ideale per | Configurazione Angular |
|---|---|---|---|---|
| Prerender (SSG) | Momento della build → file statici | ✅ Migliore | Marketing/blog/documentazione statici | RenderMode.Prerender |
| SSR | Server, per richiesta | ✅ Ottima | Contenuto dinamico che deve essere aggiornato | RenderMode.Server / @angular/ssr |
| Client (CSR) | Nel browser | ⚠️ Rischiosa | Pagine interne/autenticate (non indicizzate) | RenderMode.Client |
| Rendering dinamico | Server bot separato | Solo soluzione tampone | App legacy che non possono migrare | Puppeteer / Rendertron / prerender.io |
Comandi di configurazione
| Obiettivo | Comando |
|---|---|
| Nuovo progetto con SSR | ng new my-app --ssr |
| Aggiungere SSR a un’app esistente | ng add @angular/ssr |
| Abilitare la hydration | provideClientHydration() in app.config.ts |
Regole rapide per head e routing
- Title/description: i servizi
Title+Metadi Angular (non serve una libreria di terze parti). - Title automatici per route:
TitleStrategydel router (v14+) tramite la proprietàtitledella route. - Routing: HTML5 History API +
<base href="/">. MaiuseHash: true. - Link: veri anchor
<a href>, anche verso route caricate lazy. - JSON-LD: iniettalo tramite il token
DOCUMENTe mantienilo in un unico posto.
Nomi
- Angular Universal = vecchio pacchetto esterno (
@nguniversal/express-engine), in modalità manutenzione. @angular/ssr= lo stesso SSR, integrato nella CLI dalla v17.- AngularJS (v1.x) ≠ Angular (v2+): framework diversi; i vecchi consigli su AngularJS non si applicano.
Insidie
isPlatformBrowser()nel template@if→ mismatch di hydration → CLS. UsaafterNextRender().window/localStorage/documentsul server → crash SSR.- Bloccare
.js/.cssin robots.txt → Google non può fare il rendering.
Verificare se un’app Angular esegue davvero il rendering lato server
Il modo più veloce per capire se un URL usa CSR o SSR/prerendering è recuperare l’HTML grezzo (prima dell’esecuzione di JavaScript) e cercare il contenuto reale. Un’app Angular CSR restituisce un <app-root> quasi vuoto; una app SSR/prerenderizzata restituisce markup completo.
macOS / Linux
# Raw HTML as the server sends it — this is the "first fetch", pre-JS
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page/ -o raw.html
# Is your real headline in the raw HTML? Empty result = CSR with no SSR
grep -o "Your headline text" raw.html
# A near-empty <app-root> is the tell-tale CSR signature
grep -o "<app-root></app-root>" raw.htmlWindows (PowerShell)
$ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Invoke-WebRequest -Uri "https://example.com/page/" -UserAgent $ua -OutFile raw.html
Select-String -Path raw.html -Pattern "Your headline text"
Select-String -Path raw.html -Pattern "<app-root></app-root>"Se la headline manca e vedi un <app-root> nudo, il contenuto dipende dal rendering: aggiungi SSR o prerendering. Per il DOM renderizzato, usa «View Crawled Page → rendered HTML» in URL Inspection; un semplice curl non può eseguire JavaScript.
Confermare di non bloccare JavaScript/CSS di Angular in robots.txt
macOS / Linux
curl -sL https://example.com/robots.txt | grep -iE "disallow.*\.(js|css)|Disallow:\s*/(assets|dist|main)"Una Disallow che corrisponde al tuo bundle significa che Google non può eseguire correttamente il rendering della pagina: quasi sempre è un errore.
Strumenti per fare debug della SEO di Angular
- URL Inspection (Google Search Console) — la fonte di verità. Esegui un Live Test, poi leggi l’HTML renderizzato (il DOM dopo che Googlebot ha eseguito il tuo JavaScript), lo screenshot, le risorse della pagina (ciò che è stato caricato rispetto a ciò che è stato bloccato) e i messaggi della console JavaScript.
- Rich Results Test — conferma che il tuo JSON-LD sia presente nell’output renderizzato dopo ogni modifica al rendering.
curl— recupera l’HTML grezzo, prima di JS, per distinguere CSR (un<app-root>vuoto) da SSR/prerendering.- Ahrefs Site Audit (con rendering JS abilitato) — esegue il crawling con Chrome headless e confronta DOM grezzo e renderizzato, facendo emergere su larga scala metadati mancanti, canonical errati e problemi di indicizzabilità.
- Screaming Frog SEO Spider (modalità rendering JS) — confronta contenuto grezzo e renderizzato per ogni URL.
- Lighthouse / PageSpeed Insights — misura l’impatto sui Core Web Vitals della strategia di rendering (CSR rispetto a SSR/prerender).
- Angular CLI / DevTools — conferma che
outputMode, route server e hydration siano configurati come previsto.
Risorse che vale la pena consultare
I miei contenuti correlati
- JavaScript SEO: A Definitive Guide — la mia guida completa al rendering, alla parità del DOM, alla regola della direttiva più restrittiva e alle configurazioni di rendering sicure. Angular SEO è un’applicazione specifica di tutto questo.
- The Beginner’s Guide to Technical SEO — dove rendering e crawling rientrano nel quadro più ampio.
I miei interventi
- JavaScript SEO — Ungagged 2019 (SlideShare) — il rendering stateless di Googlebot, la viewport e la cache, oltre agli approcci di rendering dell’epoca. (Avvertenza permanente: la raccomandazione del rendering dinamico in quelle slide oggi è superata; Google lo ha definito una soluzione tampone.)
Dal settore
- Rendering sul web (web.dev) — il testo di riferimento di Addy Osmani e Jason Miller su SSR, CSR, rendering statico e compromessi della hydration.
- Rendering lato server e ibrido (SSR) (angular.dev) — la guida ufficiale a
@angular/ssr: route server,RenderMode, prerendering e hydration. - Introducing Angular v17 (blog del team Angular) — la release che ha reso l’SSR una funzionalità CLI di prima classe e introdotto il pacchetto
@angular/ssr. - Angular Universal (maintenance mode) (GitHub) — il pacchetto SSR storico che
@angular/ssrha sostituito, utile per capire la rinominazione. - Angular SEO Guide (Search Engine Journal, Jamie Indigo) — la guida classica all’indicizzazione in due ondate; solida sui fondamentali, anche se precedente alla rinominazione della v17.
- Guida all’SSR di Angular (Angular Architects, Alexander Thalhammer, marzo 2025) — una guida pratica e ricca di codice alla configurazione SSR; confronta gli esempi con la documentazione ufficiale
@angular/ssrper verificare i cambiamenti API successivi alla pubblicazione. - r/TechSEO — la community per il debug del rendering e dell’indicizzazione.
Anti-pattern SEO di Angular
Errori concreti che vedo ripetere nelle app Angular: ciascuno è un’abitudine da controllare direttamente, non solo un rischio teorico.
Distribuire una build solo CSR e considerarla finita
L’output predefinito di ng new non ha SSR né prerendering. È il modo più veloce per iniziare un progetto e il modo più semplice per ritrovarsi con un <app-root> vuoto al primo fetch. Perché è sbagliato: l’HTML grezzo che vede un crawler non contiene contenuti, quindi l’indicizzazione dipende interamente dal passaggio di rendering differito di Google; gli altri bot non ricevono nemmeno una seconda possibilità. Fai invece così: aggiungi @angular/ssr all’inizio del progetto (ng new my-app --ssr) oppure esegui ng add @angular/ssr su un progetto esistente, prima di distribuire qualsiasi cosa che vuoi far trovare.
Usare HashLocationStrategy per le route pubbliche
Il routing con hash (useHash: true, URL come /#/products/shoes) è ancora predefinito in alcuni tutorial e boilerplate Angular meno recenti. Perché è sbagliato: tutto ciò che segue # viene rimosso lato client prima che la richiesta raggiunga il server, quindi il server — e Googlebot — vede sempre un solo URL per l’intera app. Fai invece così: usa il routing predefinito HTML5 History API (PathLocationStrategy) con <base href="/"> in index.html.
Ramificare un template con isPlatformBrowser()
Racchiudere il contenuto in @if (isPlatformBrowser(platformId)) sembra il modo ovvio per proteggere il codice solo browser. Perché è sbagliato: il server renderizza un ramo e il client l’altro durante la hydration, creando un mismatch di hydration; Angular deve riconciliare la differenza e il risultato visibile è un layout shift che compare come CLS. Fai invece così: usa afterNextRender() per il lavoro solo browser, così il template viene renderizzato nello stesso modo su server e client.
Impostare direttamente document.title invece di usare il servizio Title
In sviluppo locale funziona, quindi è una scorciatoia facile da scegliere. Perché è sbagliato: l’accesso diretto al DOM come document.title = '...' non funziona bene con l’SSR: il server non ha un globale document nello stesso senso del browser e perdi i vantaggi dell’integrazione del router nella gestione dei title di Angular. Fai invece così: inietta il servizio Angular Title (setTitle()) oppure configura i title per route con TitleStrategy del router.
Toccare window, localStorage o document nel codice eseguito durante l’SSR
Un componente o servizio che legge localStorage o controlla window.innerWidth al momento della costruzione funziona nel browser ma manda in crash il rendering server. Perché è sbagliato: nessuno di questi globali esiste nel processo server Node che esegue la build SSR, quindi il rendering fallisce e la richiesta restituisce un errore del server oppure ricade silenziosamente in una risposta vuota. Fai invece così: proteggi il codice con afterNextRender() oppure inietta il token Angular DOCUMENT invece del globale e testa localmente la build SSR (ng build + servizio dell’output SSR), non solo ng serve.
Trattare il rendering dinamico come una soluzione permanente
Avviare Puppeteer o un servizio come Rendertron per servire ai bot uno snapshot prerenderizzato risolve il sintomo immediato. Perché è sbagliato: è un sistema aggiuntivo da mantenere, può divergere da ciò che vedono gli utenti reali e Google ha detto direttamente che è una soluzione tampone, non una soluzione a lungo termine. Fai invece così: migra a @angular/ssr o al prerendering, così ogni richiedente — bot o persona — riceve lo stesso HTML reale dalla stessa pipeline.
Quale modalità di rendering dovrebbe usare questa route?
Angular v17+ ti permette di impostare la modalità di rendering per ogni route in app.routes.server.ts. La domanda non è «SSR o prerendering per l’intera app?», ma questa, da porsi per ogni route.
Choosing a rendering mode for an Angular route
Prompt per il lavoro SEO su Angular
Prompt pronti da copiare per le attività SEO specifiche di questo articolo su Angular. Incolla l’input descritto e verifica l’output secondo il tuo giudizio: fanno risparmiare tempo sulle parti meccaniche, ma non sostituiscono i test con URL Inspection.
1. Confrontare HTML grezzo e renderizzato per una route
Incolla l’output di curl -sL <url> (HTML grezzo) e il pannello «rendered HTML» di un Live Test di URL Inspection (oppure un crawling con rendering JavaScript di Ahrefs/Screaming Frog) per lo stesso URL.
Here is the raw HTML for [URL] (fetched with curl, before JavaScript runs):
[paste raw HTML]
Here is the rendered HTML for the same URL (from Google Search Console URL
Inspection's Live Test, or a JS-rendering crawler):
[paste rendered HTML]
Compare the two. List: (1) content present in rendered but missing from raw —
this is what depends on client-side rendering, (2) any <title>, meta description,
or JSON-LD that differs between the two versions, (3) whether the raw HTML shows
a near-empty <app-root> (a sign of CSR with no SSR/prerendering).Aspettati un breve elenco di ciò che dipende dal CSR e di eventuali differenze tra title/meta/schema nelle versioni grezza e renderizzata: sono le due cose da correggere per prime.
2. Esaminare un’implementazione del servizio Title/Meta
Incolla il tuo SeoService Angular (o equivalente) che chiama i servizi Title e Meta.
Here is an Angular service that sets page titles and meta tags:
[paste the service's TypeScript code]
Check it against these rules: (1) titles are set via the Title service's
setTitle(), never document.title directly, (2) description is set with
meta.updateTag({ name: 'description', ... }) not addTag() (which can duplicate
the tag on repeat calls), (3) Open Graph tags use the property selector, not
name, (4) nothing in this code reads window/localStorage/document directly in a
way that would break during SSR. Flag any line that violates one of these and
suggest the fix.Aspettati una verifica riga per riga, con esito superato/non superato, rispetto a quelle quattro regole e uno snippet corretto per ogni elemento segnalato.
3. Verificare app.routes.server.ts per errori nella modalità di rendering
Incolla il file di configurazione delle route server.
Here is my Angular app.routes.server.ts, which sets RenderMode per route:
[paste the file]
For each route, tell me: is RenderMode.Prerender used on anything that depends
on per-request or per-user data (a mistake — it would bake stale/wrong data into
the static build)? Is RenderMode.Client used on anything that looks like public,
indexable content (a missed-SEO-opportunity)? Is RenderMode.Server used on fully
static content where Prerender would be faster and cheaper? List each route with
its current mode and whether it matches the decision rule: static data →
Prerender, must-be-fresh → Server, non-indexed internal → Client.Aspettati un esito per ogni route, con la segnalazione di quelle in cui RenderMode non corrisponde a ciò di cui la route ha davvero bisogno.
Mettiti alla prova: SEO per Angular
Cinque domande rapide su come rendere un’app Angular scansionabile e indicizzabile. Scegli una risposta per ciascuna, poi controlla.
Cronologia modifiche
Aggiornato il 11 ago 2026.
Riepilogo editoriale e dettagli registrati delle modifiche.Dettagli delle modifiche
-
Le note dettagliate sulle modifiche sono attualmente disponibili in inglese.
Confronto completo non disponibile — non è stata archiviata alcuna istantanea precedente per questa revisione.
Aggiornato il 17 lug 2026.
Riepilogo editoriale e dettagli registrati delle modifiche.Dettagli delle modifiche
-
Le note dettagliate sulle modifiche sono attualmente disponibili in inglese.
-
Le note dettagliate sulle modifiche sono attualmente disponibili in inglese.
-
Le note dettagliate sulle modifiche sono attualmente disponibili in inglese.
Confronto completo non disponibile — non è stata archiviata alcuna istantanea precedente per questa revisione.