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.

Prima pubblicazione: 26 giu 2026 · Ultimo aggiornamento: 11 ago 2026 · Advanced
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 — 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 servizi Title/Meta (o TitleStrategy del router), routing HTML5 History — mai HashLocationStrategy — 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.

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

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:

  1. 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.
  2. Core Web Vitals peggiori. L’LCP ne risente perché il browser deve scaricare ed eseguire un bundle prima di dipingere contenuto significativo.
  3. 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, localStorage o sessionStorage tra 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>.js predefinito 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/ssr

Questo 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 @defer con trigger di hydration che controllano quali confini restano disidratati nel rendering iniziale; un confine hydrate never resta 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:

  • TitlesetTitle() / getTitle().
  • MetaaddTag(), addTags(), updateTag(), getTag(), removeTag(), con selettori come name='description' o property='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="/"> in index.html.
  • Non usare mai HashLocationStrategy / useHash: true per 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

  1. Routing con hash (URL #): l’intero sito sembra un solo URL.
  2. Nessuna chiamata a Title/Meta: ogni pagina condivide lo stesso title e la stessa description.
  3. Nessun SSR/prerendering: il contenuto esiste solo dopo l’ondata di rendering differita.
  4. Blocco di .js/.css in robots.txt: Google non può eseguire il rendering e indicizza un guscio vuoto.
  5. Restituzione di 200 per una pagina non trovata: è un soft 404; restituisci un vero 404 oppure aggiungi noindex.
  6. document.title = ... invece del servizio Title.
  7. isPlatformBrowser() dentro @if nel template: mismatch di hydration → CLS.
  8. Accesso a window/localStorage/document nel codice eseguito sul server: crash SSR.
  9. Suddivisione dello schema tra HTML grezzo e DOM renderizzato.
  10. 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 curl dell’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.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.