Next.js SEO: come il rendering decide cosa vede Google

Con Next.js la decisione più importante per la SEO non si prende scrivendo il testo di una pagina, ma scegliendo come quella pagina viene generata. Static generation, server-side rendering, incremental static regeneration, client-side rendering: sono opzioni tecniche, ma ognuna determina se un crawler trova il contenuto già pronto nella risposta HTTP oppure deve eseguire JavaScript per costruirselo da solo. Un title ben scritto su una pagina che restituisce HTML vuoto vale zero.
Questo articolo non spiega da capo cos'è il rendering lato client o lato server: parte dal presupposto che tu sappia già la differenza generale. Si concentra su cosa cambia quando quella scelta la fai dentro Next.js, dove le opzioni disponibili, la loro sintassi e i loro effetti collaterali sono specifici del framework — e cambiano in modo netto tra App Router e Pages Router, il che da solo spiega buona parte delle guide contraddittorie che si trovano in giro.
Le modalità di rendering, in termini di conseguenze
Next.js offre quattro modi per produrre l'HTML di una route, e la scelta si fa per singola pagina, non per l'intero sito. La static generation (SSG) produce l'HTML al momento della build: è la scelta più sicura per la SEO perché il crawler riceve sempre una risposta completa e istantanea, ma il contenuto resta quello del momento della build finché non rifai il deploy.
Il server-side rendering (SSR) genera l'HTML a ogni richiesta, sul server. È indicato quando il contenuto cambia in base a chi chiede la pagina o deve riflettere dati aggiornati al secondo, ma paga un costo in tempo di risposta: se il server è lento a rispondere, lo è anche per il crawler, e un tempo di risposta alto consuma più rapidamente il crawl budget assegnato al sito.
L'incremental static regeneration (ISR) è un compromesso: la pagina viene servita come statica, ma Next.js la rigenera in background dopo un intervallo di tempo che definisci tu (il parametro revalidate), senza bloccare la richiesta corrente. È la scelta giusta per cataloghi con migliaia di pagine che cambiano poco alla volta, dove rigenerare tutto a ogni build sarebbe troppo lento.
Il client-side rendering (CSR) è l'unico caso in cui il server restituisce un HTML minimo e il contenuto vero compare solo dopo che il browser ha scaricato ed eseguito il bundle JavaScript. La regola pratica è semplice: qualunque contenuto che esiste solo dopo l'esecuzione di JavaScript lato client è un contenuto che stai chiedendo al crawler di andarsi a costruire da solo, con un passaggio di rendering in più, in coda, che può essere ritardato o saltato. Per una pagina che deve posizionarsi, il CSR puro è la scelta da giustificare, non quella di default.
App Router e Pages Router non sono la stessa cosa
Dal punto di vista SEO, il problema più grande di Next.js oggi non è tecnico: è che gran parte dei consigli in circolazione — thread, vecchi articoli, risposte copiate — è stata scritta per il Pages Router (la directory pages/, con getStaticProps e getServerSideProps) e viene applicata pari pari a progetti che usano l'App Router (la directory app/, con i Server Component e le funzioni async direttamente nei componenti). I due sistemi non condividono la stessa API di data fetching né lo stesso modo di gestire i metadata, quindi una guida scritta per l'uno spesso semplicemente non si applica all'altro.
Prima di seguire qualsiasi indicazione trovata online, la prima cosa da verificare è quale router sta usando il progetto che hai davanti, perché la sintassi corretta cambia radicalmente. Sull'App Router il rendering di default è lato server per ogni componente, a meno di marcarlo esplicitamente con la direttiva 'use client'; sul Pages Router il default dipende da quale funzione di data fetching esporti in ogni singolo file. Un mix dei due, che capita spesso nei progetti migrati a metà, è la fonte più comune di comportamenti incoerenti tra route dello stesso sito.
Metadata per route: title, description, canonical e Open Graph
Sull'App Router i metadata si dichiarano esportando un oggetto metadata statico da un file page o layout, oppure — quando dipendono da dati, come su una pagina prodotto o articolo — una funzione async generateMetadata che riceve i parametri della route e può interrogare la stessa fonte dati usata per renderizzare la pagina. Questo è il punto dove si decide se ogni pagina templata ha un title e una description propri o se, per errore, eredita quelli generici del layout padre.
Il canonical si imposta dentro l'oggetto alternates.canonical del metadata (o, sul Pages Router, con un tag link manuale dentro next/head), e va calcolato dinamicamente sulla URL reale della risorsa, non hardcodato. Su pagine con parametri di query, filtri o paginazione, un canonical statico o assente è uno degli errori più comuni: tutte le varianti finiscono per competere tra loro invece di consolidarsi su una sola versione indicizzabile.
L'Open Graph segue lo stesso principio: va generato per route, non impostato una volta sola a livello globale. Su un sito con centinaia di pagine templated — prodotti, articoli, categorie — l'immagine e il titolo condivisi su social devono riflettere quella pagina specifica, non un default che rende ogni condivisione identica a tutte le altre.
Gli errori che si vedono più spesso
Il più diffuso è il soft 404: quando una risorsa non esiste più — un prodotto rimosso, uno slug sbagliato — la pagina va fatta rispondere con uno stato 404 reale, chiamando notFound() nell'App Router o restituendo { notFound: true } dalle funzioni di data fetching sul Pages Router. Se questo passaggio manca, Next.js serve comunque un 200 con una pagina vuota o un messaggio generico, e quella risposta finisce indicizzata come se fosse contenuto valido, sprecando crawl budget su URL che non portano a nulla.
Il secondo è il title che non si aggiorna durante la navigazione client-side: capita quando la gestione del documento viene fatta a mano, fuori dal sistema di metadata del framework, magari con un useEffect che scrive su document.title dopo il mount. Sull'App Router usato correttamente questo non dovrebbe succedere, perché generateMetadata gestisce il title per ogni segmento di route — ma succede comunque quando il metadata è dichiarato solo nel layout radice e mai sovrascritto dalle pagine figlie.
Poi ci sono i problemi di idratazione: quando l'HTML generato dal server e quello ricostruito dal client non coincidono, React scarta parte del markup e lo ri-renderizza. Non è di per sé un segnale SEO diretto, ma se l'errore di idratazione fa fallire silenziosamente un componente, il contenuto che quel componente doveva mostrare può sparire dalla pagina finale — e a quel punto il problema di rendering diventa un problema di contenuto mancante.
Un altro errore ricorrente sono i redirect fatti lato client, con router.push() dentro un useEffect dopo un controllo di autenticazione o di lingua, invece di un redirect vero fatto a livello di middleware o di server. Un crawler che non esegue quel JavaScript, o che lo esegue in un passaggio successivo, vede il contenuto della URL originale invece di essere indirizzato alla destinazione — e soprattutto non passa nessun segnale di redirect permanente, perché tecnicamente non è mai stata inviata un'intestazione HTTP di redirect.
Infine, la sitemap che invecchia male sui siti generati staticamente: se la sitemap viene prodotta una volta in fase di build e il sito usa ISR o un export statico, le nuove pagine create dopo l'ultimo deploy non ci compaiono finché non parte una nuova build. La soluzione più solida è una route di sitemap generata dinamicamente (un file sitemap.ts nell'App Router) con una propria logica di aggiornamento, invece di un file statico dimenticato lì dal primo deploy.
Immagini e Core Web Vitals
Il componente next/image risolve automaticamente molti problemi di performance delle immagini — genera i formati moderni, calcola le dimensioni responsive, applica il lazy loading — ma questo non significa che l'immagine più importante della pagina, quella che determina il Largest Contentful Paint, sia a posto di default. Un'immagine hero caricata con lazy loading, perché è quella che il componente applica di default a tutte le immagini, ritarda proprio l'elemento che il Core Web Vitals misura come LCP.
Per l'immagine principale sopra la piega vale la regola opposta: va marcata con l'attributo priority, così Next.js la precarica invece di rimandarla, e va servita con dimensioni (sizes) corrette per il layout reale, non con un valore generico copiato da un altro progetto. Anche con il framework che gestisce la parte tecnica, la responsabilità di identificare quale immagine è quella critica per ogni template resta editoriale, non automatica.
Routing internazionalizzato
Per un pubblico italiano, o comunque non anglofono, il routing internazionalizzato pesa più che per un sito rivolto solo a lettori di lingua inglese, perché entra in gioco quasi sempre. Sull'App Router l'i18n si costruisce tipicamente con un segmento dinamico [lang] nella struttura delle cartelle e un middleware che rileva la lingua della richiesta; il Pages Router aveva invece un supporto i18n integrato nella configurazione, spesso abbinato a librerie come next-i18next.
Il punto critico non è tecnico ma strategico: la rilevazione automatica della lingua, tipicamente basata sull'header Accept-Language o su un cookie, non deve mai essere l'unico modo per raggiungere la versione italiana di una pagina. Un crawler può presentarsi senza quell'header nella forma attesa da un browser reale, quindi ogni lingua deve avere URL proprie, raggiungibili e collegate tra loro con i tag hreflang corretti — il redirect automatico è una comodità per l'utente umano, non un sostituto di URL indicizzabili per ogni versione linguistica.
Verificare cosa viene servito davvero
L'unico modo affidabile per sapere se una pagina Next.js è SEO-friendly non è guardarla nel browser, dove React ha già fatto il suo lavoro e tutto sembra a posto: è recuperare l'HTML grezzo così come arriva dal server, prima di qualsiasi esecuzione di JavaScript, e controllare se il contenuto che conta — title, testo principale, link — è già lì dentro.
Un curl sull'URL della pagina, senza eseguire nulla, mostra esattamente quella risposta iniziale; se title e contenuto principale mancano da quell'output ma compaiono nel browser, la pagina dipende dal client-side rendering per essere completa. Lo strumento di ispezione URL della Search Console aggiunge un altro livello utile, mostrando l'HTML renderizzato così come lo ha visto Google, che è un punto di riferimento più affidabile della semplice apertura in un browser normale.
Vale la pena farlo per ogni tipo di route del sito — statica, dinamica, protetta da autenticazione — perché la modalità di rendering può cambiare da un template all'altro nello stesso progetto, e una verifica fatta su una sola pagina non dice nulla sulle altre.
Domande frequenti
Next.js è di per sé meglio per la SEO rispetto a un'app React tradizionale?
Solo se sfrutti le modalità di rendering che generano HTML lato server o in fase di build. Un'app Next.js configurata per fare tutto in client-side rendering ha, ai fini della SEO, gli stessi limiti di una single page application React qualunque: il framework offre l'opzione, non la impone.
Meglio SSG o ISR per un sito con molte pagine?
Dipende da quanto spesso cambia il contenuto. Se le pagine restano stabili per settimane, la SSG pura è più semplice e altrettanto efficace. Se il catalogo cambia di continuo e rigenerare tutto a ogni build diventa lento, l'ISR permette di aggiornare le singole pagine senza un nuovo deploy completo.
Passare da Pages Router ad App Router migliora la SEO?
Non automaticamente. L'App Router offre un'API di metadata più solida e rendering lato server come default, ma i vantaggi si materializzano solo se la migrazione viene fatta implementando davvero generateMetadata per route e non lasciando i default ereditati dal layout.
Il rendering client-side viene comunque indicizzato da Google?
Spesso sì, perché Google esegue un passaggio di rendering JavaScript, ma non è istantaneo né garantito per ogni pagina, e altri motori o strumenti di terze parti che leggono l'HTML grezzo potrebbero non vedere nulla. Per contenuto che deve posizionarsi in modo affidabile, non conviene dipendere da quel passaggio aggiuntivo.