L'errore di canonical che nasconde le tue traduzioni

agentready.md pubblica in dieci lingue con un prefisso nel percorso. Ogni pagina esiste come /about, /es/about, /ja/about e altre sette. Per mesi tutte e nove le traduzioni hanno servito la stessa riga nel loro <head>:

<link rel="canonical" href="https://agentready.md/about">

/es/about stava dicendo ai motori di ricerca: sono un duplicato della pagina inglese, indicizza quella. Lo stesso /ja/about. E le altre sette. Dieci lingue, una sola indicizzabile.

Cosa dichiara davvero un canonical

rel="canonical" non è un suggerimento su quale URL sia più ordinato. È un'affermazione: questa pagina e quella che nomino sono la stessa, e quella che nomino è la copia da indicizzare. Link, segnali di posizionamento e la voce stessa nell'indice vanno tutti alla destinazione. Questa pagina smette di competere.

È il comportamento giusto quando /prodotto?colore=blu punta a /prodotto. È quello sbagliato per una traduzione. Una pagina in spagnolo non è un duplicato di quella inglese: è un'altra pagina, per un altro lettore, e deve posizionarsi da sola sulle ricerche in spagnolo.

Perché è sopravvissuto così a lungo

Il nostro router toglie il prefisso di lingua prima del routing, quindi una richiesta a /es/about arriva all'handler come /about e la lingua viaggia a parte. È una scelta sensata, perché le rotte si dichiarano una volta e valgono per tutte le lingue, ma lascia request.url senza prefisso, e il partial dell'<head> costruiva il canonical proprio da quel percorso nudo.

In inglese il bug produce il risultato corretto, perché l'inglese non ha prefisso. Tutte le pagine che guardi mentre sviluppi nella tua lingua sono giuste. Si rompe solo nelle nove che non leggi.

La correzione è una chiamata, che rimette il prefisso prima di stampare l'URL:

<%# request.url ha già perso il prefisso /es/ — localizedUrl lo rimette. %>
<% const canonicalHref = localizedUrl(canonicalPath); %>
<link rel="canonical" href="<%= baseUrl %><%= canonicalHref %>">

La regola: ogni versione è canonical di se stessa

Canonical autoreferenziale, sempre. /es/about dichiara /es/about. /ja/about dichiara /ja/about. Per le traduzioni non ci sono eccezioni. E se due versioni si somigliano moltissimo, la stessa scheda prodotto con tre parole cambiate, non cambia niente: la lingua non è duplicazione.

hreflang: ogni versione le elenca tutte

Il canonical dice quale pagina indicizzare. hreflang dice quale delle pagine indicizzate mostrare a quale lettore. Servono entrambi, e ci sono due regole di hreflang che quasi tutti sbagliano.

Ogni versione le elenca tutte, se stessa compresa. Dieci lingue sono dieci link alternate su tutte e dieci le pagine, autoriferimento incluso. La mancanza dell'autoriferimento è il difetto più comune in un insieme per il resto corretto.

I link devono essere reciproci. Se la pagina inglese nomina quella spagnola e la spagnola non ricambia, il motore scarta la relazione, spesso l'insieme intero, perché non può verificarla. Chiunque può dichiararsi traduzione di qualcun altro. Solo l'altra pagina lo conferma.

Generali da un unico elenco invece di mantenerne dieci:

<% ['en','es','fr','de','pt','it','ja','zh','ko','ru'].forEach(l => { %>
<link rel="alternate" hreflang="<%= l %>" href="<%= baseUrl %><%= localizedUrl(canonicalPath, l) %>">
<% }) %>
<link rel="alternate" hreflang="x-default" href="<%= baseUrl %><%= canonicalPath %>">

Usa il codice di lingua da solo (es) quando una versione serve tutti i parlanti. Aggiungi la regione (es-ES, pt-BR) solo se pubblichi davvero versioni diverse per mercato; inventarsi regioni che non esistono spezza l'insieme per niente.

x-default non è un'altra traduzione

x-default indica la versione da servire a un lettore la cui lingua non pubblichi. Qualcuno cerca in olandese e l'olandese non ce l'hai: si prende quello che punta x-default.

Non è «la lingua più importante» né un'altra voce della rotazione. Il nostro resta sull'URL inglese senza prefisso, che è già il ripiego di tutto il sito. Puntarlo a una pagina con lingua propria, /es/about, manda in spagnolo ogni lettore per cui non hai una traduzione, ed è peggio del comportamento predefinito.

Controllalo in una riga

L'intero guasto si vede dal terminale. Cicla sulle tue lingue e stampa cosa dichiara ogni pagina:

for p in "" es/ fr/ de/ pt/ it/ ja/ zh/ ko/ ru/; do
  printf '%-4s ' "${p:-en}"
  curl -s "https://esempio.com/${p}about" | grep -o '<link rel="canonical"[^>]*>'
done

L'output corretto nomina in ogni riga il proprio percorso:

en   <link rel="canonical" href="https://esempio.com/about">
es   <link rel="canonical" href="https://esempio.com/es/about">
fr   <link rel="canonical" href="https://esempio.com/fr/about">

Dieci righe con lo stesso URL sono il bug di questo articolo. Lancialo sui template che hai toccato, non solo sulla home: il canonical di solito si costruisce per layout, e un sito con tre layout può essere giusto in due.

Gli altri modi in cui si rompe

Parametri di tracciamento dentro il canonical. Se costruisci il canonical dall'URL della richiesta così com'è, chi arriva con ?utm_source=newsletter riceve un canonical che punta a ?utm_source=newsletter. Ogni condivisione crea un URL nuovo che si dichiara canonical, cioè un insieme illimitato di URL per una sola pagina. Costruiscilo dal percorso e rimetti solo i parametri che identificano davvero un'altra pagina, un elenco filtrato o una paginazione, in un ordine fisso, così una pagina non può scrivere il proprio nome in due modi.

Canonical che punta a un redirect. Se /es/about dichiara /es/about/ e quello fa un 301 verso /es/about, hai dato al crawler un giro da risolvere e un motivo per diffidare del segnale. Punta all'URL che risponde 200.

Due canonical che si contraddicono. Il <link> nell'HTML e un header HTTP Link: <...>; rel="canonical" sono entrambi validi, e CDN, plugin e reverse proxy li aggiungono volentieri. Quando non coincidono l'esito è indefinito e quasi mai quello che volevi. Guarda anche gli header:

curl -sI https://esempio.com/es/about | grep -i "^link:"

Non vedere niente è la risposta buona, a meno che non ce l'abbia messo tu apposta.

Analizza il tuo sito · Generatore di meta tag · Checklist di preparazione all'IA