JavaScript SEO: come Google indicizza SPA e siti React
La coda di rendering a due onde di Google, le trappole del CSR, la differenza fra SSR/SSG/ISR e i metodi pratici per verificare che il tuo sito JS sia indicizzato correttamente.
Come Google renderizza JavaScript: come funziona
Per una normale pagina HTML, Google fa un passo: scarica l'HTML e vede subito il contenuto. Per una pagina JavaScript, i passi sono tre: scaricare l'HTML → caricare il bundle JS → eseguirlo in Chromium headless per ottenere il DOM finale.
Google usa il proprio engine browser basato su Chromium — mantenuto aggiornato con aggiornamenti regolari. Quindi le funzionalità JavaScript moderne (ES2020+, import dinamico, WebAssembly) sono comprese da Googlebot. Il problema non è il supporto della sintassi, ma il ritardo del rendering e i vincoli di risorse durante la scansione.
Il rendering in numeri
Timeout di rendering
Se il JS non viene eseguito entro ~3–4 secondi, Google indicizza una pagina renderizzata parzialmente
Tempo CSS
Le risorse che caricano in più di 150 ms possono bloccare il rendering e danneggiare l'efficienza della scansione
Indicizzazione
I contenuti JS raggiungono l'indice con ritardo rispetto ai contenuti HTML — a volte giorni o settimane
Google Evergreen
Googlebot si aggiorna all'ultimo Chromium stabile — le funzionalità JS moderne sono pienamente supportate
Indicizzazione a due onde: perché i contenuti JS arrivano dopo
Google divide la scansione in due fasi. La prima ondata è il crawling: Googlebot scarica l'HTML e tutti i link scoperti. La seconda ondata è il rendering: il bot mette in coda le pagine per il rendering, esegue il JS e indicizza il DOM finale.
Fra queste onde possono passare da ore a settimane — a seconda dell'autorevolezza del sito, del crawl budget e delle dimensioni della coda di rendering di Google. Se i tuoi testi chiave, gli H1, i meta tag o i link vengono generati solo via JS — arriveranno nell'indice con ritardo.
CSR — la trappola SEO principale
Il Client-Side Rendering (CSR) è quando il server invia un <div id="root"></div> vuoto e tutto l'HTML viene costruito nel browser dopo il caricamento del bundle JS. React, Vue e Angular in modalità SPA funzionano così di default.
Dal punto di vista SEO, il CSR è un problema per diversi motivi: 1) il rendering avviene solo nella seconda ondata; 2) le risorse bloccate al crawler (script di terze parti, CDN con robots.txt) non si caricano — la pagina si renderizza in modo incompleto; 3) qualsiasi errore JS = pagina vuota nell'indice.
<!-- CSR: what Google sees in the first wave -->
<html>
<head>
<title></title> <!-- empty -->
</head>
<body>
<div id="root"></div> <!-- no content -->
<script src="/bundle.js"></script>
</body>
</html><title>, <meta name="description"> e <h1> solo via JS lato client. Google riceve valori vuoti o di default durante il crawling iniziale e indicizza la pagina senza.SSR, SSG e ISR: quale scegliere per la SEO
La soluzione ai problemi del CSR è la generazione HTML lato server. Esistono tre approcci principali, ciascuno ottimale per il proprio scenario.
Confronto degli approcci di rendering
| Approccio | HTML sul server | SEO | Dati dinamici |
|---|---|---|---|
| CSR | No | Scadente | Pieno |
| SSR | Sì (a ogni richiesta) | Eccellente | Alto |
| SSG | Sì (alla build) | Eccellente | No (serve rebuild) |
| ISR | Sì (a timer) | Eccellente | Via revalidazione |
| Approccio | Ideale per |
|---|---|
| CSR | SPA autenticate, dashboard |
| SSR | Pagine personalizzate, cataloghi |
| SSG | Blog, landing page, documentazione |
| ISR | Cataloghi prezzi, news |
SSR (Server-Side Rendering) — l'HTML viene generato sul server a ogni richiesta. Massima freschezza dei dati, ma maggiore carico sul server. Next.js: generateMetadata e i componenti async nell'App Router forniscono SSR di default.
SSG (Static Site Generation) — l'HTML viene generato una sola volta alla build e servito come file statico. TTFB più veloce, ideale per la SEO. Limite: i cambi di dati richiedono una rebuild.
ISR (Incremental Static Regeneration) — un ibrido: le pagine vengono costruite in statico, ma dopo un periodo di revalidazione (revalidate: 3600) il server le rigenera in background alla richiesta successiva. In Next.js è l'approccio standard per la maggior parte delle pagine.
// Next.js App Router — SSG + ISR
export const revalidate = 3600; // revalidate every hour
export async function generateMetadata(): Promise<Metadata> {
return {
title: 'Product Catalogue',
description: 'Full range with up-to-date prices',
};
}
// All critical content is server-rendered:
export default async function CatalogPage() {
const products = await fetchProducts(); // server fetch
return (
<main>
<h1>Product Catalogue</h1>
{products.map(p => <ProductCard key={p.id} {...p} />)}
</main>
);
}Link dinamici e navigazione
Googlebot capisce solo i link in formato <a href="/url">. I link implementati via onClick, button, data-href o window.location sono invisibili al crawler. Questo significa che le pagine raggiungibili solo attraverso tale navigazione potrebbero non essere indicizzate.
- Usa
<a href>per tutti i link di navigazione — nonbutton onClickconrouter.push() - Paginazione via parametri URL (
?page=2) — Googlebot scansiona questi URL se compaiono nell'HTML - Router SPA: Next.js
<Link>e React Router<Link>generano tag<a>corretti sul server - La navigazione con hash (
#ancora) non crea URL separati nell'indice — non usarla per contenuti che devono posizionarsi in autonomia - Menu dinamici: se le voci di menu si renderizzano da un'API dopo il caricamento — Google potrebbe non scoprirle fino al rendering
Lazy load e infinite scroll
Le immagini con loading="lazy" vengono caricate da Googlebot — il crawler moderno supporta il lazy loading. Ma c'è una condizione: l'immagine deve essere nel DOM con un attributo src corretto durante l'SSR. Se src viene impostato da JavaScript nel browser — il crawler vede un tag vuoto.
L'infinite scroll è un problema classico. Google potrebbe caricare il primo «schermo» di contenuti, ma le porzioni successive caricate via IntersectionObserver non sono garantite nell'indice. Soluzione: duplica i contenuti via paginazione con URL reali (/catalog?page=2) o aggiungi in HTML link alle pagine caricate in lazy.
| Pattern | Googlebot lo vede? | Raccomandazione |
|---|---|---|
| <img src="/img.webp" loading="lazy"> | Sì | Usalo |
| <img data-src="/img.webp"> (JS imposta src) | No | Evita |
| Infinite scroll senza URL | Solo il primo schermo | Aggiungi la paginazione |
| Contenuti dietro il pulsante «Mostra altro» | No | Mostra una parte in HTML |
| Contenuti a tab (tab nascoste) | Sì, con ritardo | Duplica in HTML |
Diagnosi: come verificare il rendering JavaScript
La domanda diagnostica chiave è: «Cosa vede Googlebot sulla mia pagina?». Esistono diversi strumenti per rispondere.
Strumenti di diagnosi
- Google Search Console → Controllo URL: mostra uno screenshot della pagina dopo il rendering di Googlebot, una lista di risorse e link scoperti. Il metodo di verifica più diretto.
- «Visualizza come Googlebot» in GSC: esegue il vero crawler e restituisce l'HTML dopo il rendering — confrontalo con l'HTML sorgente.
curlowget: ottieni l'HTML grezzo senza JS. Se i meta tag e i contenuti chiave sono nell'output dicurl— saranno nella prima ondata di indicizzazione.- Disabilitare JS in Chrome DevTools (F12 → Ctrl+Shift+P → «Disable JavaScript»): modo rapido per vedere la pagina come farebbe il crawler senza rendering.
- Screaming Frog con rendering: l'impostazione «Spider > Configuration > Rendering > Googlebot» permette di scansionare il sito col rendering JS — vedendo i link trovati post-render.
# Check what Google sees without JS rendering:
curl -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page
# Find all title / meta description / h1 in HTML response:
curl -s https://example.com/page | grep -E '<title>|<meta name="description"|<h1'Checklist JavaScript SEO
Controlli chiave
- Title e meta description presenti nella risposta HTML del server (SSR/SSG), non generati da JS
- H1, H2 e il contenuto testuale principale accessibili senza JavaScript
- Link di navigazione usano
<a href>, nononClick/button - Dati strutturati (JSON-LD) in
<head>nella risposta HTML - Immagini con
srcstatico in HTML,loading="lazy"solo per immagini non-LCP - Robots.txt non blocca i file JS/CSS necessari al rendering
- Nessun errore JS che rompa il rendering (controlla la Console di Chrome DevTools sulla pagina)
- Core Web Vitals: LCP, INP, CLS rispettano le soglie di Google
- Paginazione via parametri URL, non solo via stato JS
- URL canonical (
<link rel="[canonical](/glossary/canonical)">) in HTML, non impostati via JS
FAQ
Google riesce proprio a indicizzare JavaScript?
Un sito React/Next.js è automaticamente ben indicizzato?
Servono prerendering o rendering dinamico?
Posso usare React.lazy e code splitting?
Come verifico che il mio sito sia indicizzato correttamente?
Check it with SpiderHead
Free and local: run the audit on your own machine.



