Diario di bordo · Fase 0 · 7/7
SEO multilingue e GEO: 6 locale, SSR ed estrazione IA
Sei URL indicizzabili per lingua, un hreflang BCP47 bidirezionale, un grafo JSON-LD collegato da @id e un llms.txt: come aubia.dev si dà a leggere ai motori come alle IA.
- seo
- i18n
- geo
- inertia

Un sito vetrina pre-lancio ha un solo compito sul versante visibilità: essere trovabile, in più lingue, dai motori classici come dai motori generativi che oggi rispondono al posto dei dieci link blu. aubia.dev è servito in sei lingue, in rendering lato server, con un markup pensato per l'estrazione. Quest'ultimo articolo della serie smonta quel meccanismo. E il blog che sta leggendo gira sulle stesse regole.
Sei locale, una URL per lingua
Il sito è instradato sotto un prefisso di locale: /fr, /en, /es, /de, /it, /pt. Ogni lingua ha quindi la propria URL indicizzabile, mai un parametro o un cookie che nasconderebbe il contenuto ai crawler. Un middleware risolve il locale prima del routing, a partire dal segmento dell'URL, dal cookie, poi dall'header Accept-Language. La radice / reindirizza alla lingua rilevata migliore.
Per il pubblico internazionale al di fuori delle regioni coperte, x-default punta all'inglese. Il rendering avviene in SSR tramite Inertia: l'HTML completo parte dal primo byte, il che è un invariante non negoziabile per il SEO. Un motore che visita una pagina ne riceve il contenuto renderizzato, non un guscio vuoto in attesa dell'idratazione JavaScript.
Queste sei lingue non hanno tutte lo stesso status, e mi assumo la gerarchia. Il francese è la mia sorgente editoriale, scritta a mano. L'inglese è un pivot tradotto manualmente, perché è la lingua del mio pubblico internazionale e non tollera approssimazioni. Spagnolo, italiano, tedesco e portoghese sono poi tradotti automaticamente da quel pivot inglese. E il testo più critico, il titolo hero, il claim, gli argomenti chiave, resta escluso dalla traduzione automatica e passa sempre dalla mia mano.
Dietro queste sei lingue, non c'è alcuna libreria i18n di terze parti. La soluzione npm mainstream si è rivelata incompatibile con React 19 e rischiava di far trapelare lo stato tra le richieste sotto il server persistente di Octane. Così il motore i18n sta in un hook fatto in casa di una cinquantina di righe, collegato alla condivisione nativa delle props di Inertia, senza una sola dipendenza aggiunta.
L'hreflang, in entrambi i sensi
L'hreflang dice a un motore: questa pagina esiste anche in queste altre lingue, a queste URL precise. La regola che fa fallire la maggior parte delle implementazioni è la bidirezionalità: ogni versione deve riferirsi a sé stessa e riferirsi a tutte le altre. Uso codici BCP47 completi, più precisi di un codice breve, per sciogliere l'ambiguità di paese tra pt-PT e pt-BR.
La sitemap dinamica produce questa maglia per ogni pagina pubblicata, con un link alternate per locale:
foreach (self::LOCALE_TO_BCP47 as $altLocale => $bcp47) {
$altLoc = $baseUrl . '/' . $altLocale . $path;
$xml .= '<xhtml:link rel="alternate" hreflang="' . $bcp47 . '" href="' . $altLoc . '"/>';
}
Sul versante pagina, il componente SeoHead posa gli stessi segnali nell'<head>, ma solo sulle pagine indicizzabili. Le pagine effimere, come la conferma della lista d'attesa, sono in noindex: aggiungervi un canonical o un hreflang invierebbe un segnale contraddittorio.
Un grafo JSON-LD collegato da @id
I dati strutturati di Aubia formano un solo @graph, dove ogni entità è identificata da un @id stabile: l'Organization, il WebSite, la SoftwareApplication e la Person fondatrice si puntano a vicenda. Questo cablaggio aiuta i motori e le IA di ricerca a capire che questi oggetti descrivono la stessa entità, e non quattro cose slegate.
const jsonLd = JSON.stringify({
'@context': 'https://schema.org',
'@graph': [
{ '@type': 'Organization', '@id': `${SITE_URL}/#organization`, /* ... */ },
{ '@type': 'Person', '@id': `${SITE_URL}/#founder`, /* ... */ },
{ '@type': 'WebSite', '@id': `${SITE_URL}/#website`, /* ... */ },
...additionalJsonLd,
],
});
Il punto di estensione è additionalJsonLd: ogni pagina inietta i propri nodi nel grafo condiviso. La landing vi aggiunge il suo blocco FAQPage. Un articolo del blog vi aggiunge il suo BlogPosting e il suo breadcrumb, senza toccare la base Organization/WebSite.
GEO: scrivere per i motori che leggono
Ho scritto queste pagine per essere citate, non solo posizionate: quando un'IA risponde al posto dei dieci link blu, ciò che conta è che possa estrarre un intero passaggio senza deformarlo. Due leve concrete su aubia.dev.
La prima è un file public/llms.txt alla radice: un riepilogo strutturato del prodotto, del suo posizionamento e del suo stato, scritto per essere letto da una macchina. Dichiara cos'è Aubia, la sua differenza di cross-review, il suo pubblico e la sua roadmap, in testo semplice senza rumore di interfaccia.
La seconda è una regola di scrittura, applicata a questo stesso articolo: sezioni autosufficienti. Ogni titolo H2 apre su una definizione, poi si sviluppa in un blocco di circa centocinquanta parole che regge da sé, fuori dal contesto della pagina. Un motore che affetta la pagina in estratti deve poter citare qualsiasi blocco senza che il senso crolli. Liste brevi e titoli espliciti completano il lavoro.
Il ciclo meta: questo blog applica le proprie regole
Questo blog è esso stesso la dimostrazione di tutto quanto precede. È in flat-file: ogni articolo è un file Markdown nel repo, convertito in HTML al momento del parsing, senza database, fedele alla presa di posizione della Fase 0. Pubblicare un articolo significa fare il merge di un branch, nulla di più.
La pubblicazione è programmata dalla data nel front matter: il modulo tiene ogni articolo in cache, ma al momento della richiesta filtra quelli la cui data è passata. Un articolo datato a un lunedì compare quel lunedì, senza un redeploy, e resta invisibile prima: assente dall'indice, dal feed RSS, dalla sitemap, e in 404 sulla sua URL.
Ogni articolo pubblicato si guadagna la sua voce nella sitemap con la sua vera data di ultima modifica, le sue alternative di lingua, il suo markup BlogPosting e il suo breadcrumb. Lo stesso meccanismo di sei locale, di hreflang bidirezionale e di grafo JSON-LD che ho descritto per la landing si applica, articolo per articolo, a queste pagine.
Questo era l'ultimo capitolo della serie sulla costruzione della Fase 0. Ho costruito tutta la Fase 0 a questo livello di dettaglio, e i suoi riscontri decideranno il seguito. La lista d'attesa è il posto per seguirla da vicino.
Questo cantiere si racconta qui, articolo dopo articolo. Salite a bordo: i vostri riscontri disegneranno il seguito.
Iscriviti alla lista d'attesa