
Audit GEO di un sito: sei errori comuni nei dati strutturati e come correggerli
Cosa emerge applicando una checklist GEO a un sito reale: entità duplicate, JSON-LD senza escape, date mancanti, argomenti ambigui, niente Markdown e crawler AI bloccati dal CDN.
Una checklist GEO è facile da scrivere e difficile da rispettare. Negli articoli su cos’è la GEO, GEO vs SEO e dati strutturati e AI-first web ho elencato cosa serve perché un motore generativo capisca e citi un sito. Qui faccio il passo successivo: cosa succede quando quella checklist viene applicata davvero a un sito esistente, e quali errori saltano fuori.
Il sito di partenza era in buone condizioni: HTML generato lato server, JSON-LD sugli articoli, sitemap con date reali, hreflang per le lingue, un llms.txt. Eppure sei punti non tornavano. Sono errori che si ritrovano quasi ovunque, perché nascono dalla crescita normale di un progetto, non dalla disattenzione.
I sei problemi in sintesi
| # | Problema | Perché conta |
|---|---|---|
| 1 | Autore ridefinito in ogni pagina, senza @id |
Per un motore sono tante persone omonime, non un’entità sola |
| 2 | Editore con lo stesso nome dell’autore ma di tipo diverso | Una seconda entità che confonde invece di chiarire |
| 3 | JSON-LD inserito nella pagina senza escape | Un </script> in un titolo o in una risposta chiude il tag |
| 4 | dateModified presente solo per gli articoli aggiornati |
Google raccomanda di indicarla sempre |
| 5 | Argomento espresso solo come tag | “React” o “Swift” sono stringhe, non entità disambiguate |
| 6 | Nessuna versione testuale pulita delle pagine | Agenti e tool devono estrarre il testo dall’HTML, con layout e rumore |
A questi se n’è aggiunto un settimo, che nessuna verifica sul codice avrebbe trovato: il CDN davanti al sito rispondeva in modo diverso a seconda del client.
1 e 2: un’identità frammentata
Ogni tipo di pagina aveva il proprio blocco JSON-LD, scritto in momenti diversi: home, pagina “chi sono”, pagina servizi, articoli. La stessa persona compariva con qualifiche diverse, a volte con i profili sameAs, a volte senza. E negli articoli l’editore era un Organization con il nome della persona: due entità diverse con lo stesso nome.
Nessun blocco era sbagliato da solo. Insieme descrivevano un’identità incoerente, che è esattamente ciò che un sistema basato su entità fatica a ricostruire.
La correzione è definire le entità del sito una volta sola, con un @id stabile, e farle richiamare dalle pagine:
{
"@context": "https://schema.org",
"@graph": [
{ "@type": "Person", "@id": "https://example.com/chi-sono/#person", "name": "Mario Rossi", "sameAs": ["https://github.com/mariorossi"] },
{ "@type": "WebSite", "@id": "https://example.com/#website", "publisher": { "@id": "https://example.com/chi-sono/#person" } },
{
"@type": "BlogPosting",
"@id": "https://example.com/articolo/#article",
"author": { "@id": "https://example.com/chi-sono/#person" },
"publisher": { "@id": "https://example.com/chi-sono/#person" },
"isPartOf": { "@id": "https://example.com/#website" }
}
]
}
Un blog personale ha come editore la persona stessa, e schema.org ammette un Person come publisher. Se invece pubblica un’azienda, l’editore è l’Organization, sempre con il proprio @id. L’importante è che la scelta rispecchi la realtà e sia la stessa ovunque.
Un dettaglio utile: se una pagina elenca profili aggiuntivi (per esempio una pagina “link”), non serve creare un secondo Person. Basta aggiungere sameAs allo stesso @id: i parser JSON-LD uniscono i nodi con lo stesso identificatore.
3: l’escape che manca quasi sempre
Il modo più comune di inserire il JSON-LD in un template è serializzare un oggetto e scriverlo dentro <script type="application/ld+json">. Ma JSON.stringify non fa escape di <: se un titolo, una descrizione o una risposta FAQ estratta dal testo contiene </script>, il tag si chiude in anticipo e il resto viene interpretato come HTML.
const json = JSON.stringify(data).replace(/</g, "\\u003c");
< è JSON valido e ogni parser lo legge come <, ma per il browser non è più l’inizio di un tag. Il rischio è basso quando i contenuti li scrivi tu, ma diventa concreto appena arrivano da un CMS, da più autori o da articoli che parlano proprio di HTML e sicurezza.
4: date sempre presenti, mai inventate
dateModified va indicata sempre. Se un contenuto non è mai stato aggiornato, coincide con datePublished. Quello da evitare è l’opposto: usare la data dell’ultimo deploy. Cambia a ogni pubblicazione anche senza modifiche reali, e i motori smettono presto di considerarla un segnale.
5: dagli tag alle entità
I tag sono già un dato strutturato, ma restano stringhe. Collegarli a un’entità univoca con about e sameAs toglie l’ambiguità:
"about": [
{ "@type": "Thing", "name": "React", "sameAs": "https://en.wikipedia.org/wiki/React_(software)" }
]
Basta una piccola tabella di corrispondenza tag → pagina Wikipedia o Wikidata, limitata ai tag che corrispondono davvero a qualcosa di preciso. Tag generici come “errori”, “novità” o “strumenti” vanno lasciati fuori: un about sbagliato è peggio di un about assente.
6: una versione testuale per chi non è un browser
Agenti, assistenti e strumenti di sviluppo leggono sempre più spesso le pagine in modo diretto. Una versione Markdown di ogni articolo, raggiungibile allo stesso URL con .md, è molto più facile da elaborare dell’HTML completo con menu, footer e script. La si annuncia nell’head:
<link rel="alternate" type="text/markdown" href="/articolo.md" />
Tre accorgimenti:
- Ripulire davvero il contenuto. Se i testi sono scritti con componenti (box, embed, avvisi), vanno convertiti in Markdown semplice: un avviso diventa una citazione, un embed un link o niente. I blocchi di codice invece vanno lasciati intatti.
- Servirli con il tipo giusto,
text/markdown. Su un sito statico spesso è l’hosting a decidere il Content-Type in base all’estensione, quindi va configurato lì. - Non farli indicizzare. Un header
X-Robots-Tag: noindexevita che i motori di ricerca vedano una copia di ogni pagina. L’URL da citare resta quello HTML.
Infine conviene citarli nel file llms.txt, se ne hai uno.
7: il CDN che decide al posto tuo
Una verifica di dieci secondi ha rivelato un comportamento inatteso: una richiesta con un client generico riceveva un 403, mentre le stesse richieste con lo user agent dei crawler AI ricevevano 200.
curl -s -o /dev/null -w '%{http_code}\n' -A "GPTBot" https://example.com/
In quel caso andava bene così. Ma è il tipo di impostazione che un CDN o un WAF può cambiare per te: diversi fornitori offrono il blocco dei crawler AI con un clic, e alcuni lo propongono come predefinito per i nuovi siti. Un robots.txt perfetto non serve a nulla se il firewall risponde 403 prima.
Un test con user agent finto mostra solo come la regola tratta lo user agent, non come vengono trattati i bot verificati in base all’indirizzo IP. Per quello servono i log o le statistiche del CDN. Ma come controllo periodico è il più economico che ci sia.
robots.txt: una scelta esplicita
Lasciare Allow: / per tutti è una decisione legittima per chi vive di essere letto e citato. Chi ha contenuti con valore commerciale diretto può invece separare l’addestramento dei modelli dalla ricerca con citazione:
User-agent: GPTBot
User-agent: ClaudeBot
User-agent: Google-Extended
Disallow: /
User-agent: OAI-SearchBot
User-agent: Claude-SearchBot
User-agent: PerplexityBot
Allow: /
Google-Extended non influisce su Google Search né sulle AI Overviews: controlla solo l’uso dei contenuti per i modelli Gemini.
Come verificare il risultato
- Validatori: Schema Markup Validator e Rich Results Test su una pagina per tipo (articolo, home, chi sono, servizi).
- Controllo automatico dopo la build: ogni blocco JSON-LD deve essere JSON valido e nessun
@iddeve comparire due volte nello stesso grafo. Sono poche righe di script, e intercettano gli errori prima del deploy. - Accesso reale: la verifica con gli user agent dei crawler, ripetuta ogni tanto.
Poi c’è la parte che richiede pazienza. Le modifiche tecniche entrano in gioco ai successivi passaggi dei crawler; il riconoscimento come entità autorevole richiede mesi. Nel frattempo si misura quello che si può: il traffico con referrer dagli assistenti AI, una lista fissa di domande ripetuta ogni mese, le richieste alle versioni .md.
Checklist dell’audit
- Un solo nodo per persona e organizzazione, con
@idstabile, richiamato da tutte le pagine - Editore coerente con la realtà, lo stesso in tutto il sito
- JSON-LD con
<sottoposto a escape -
dateModifiedsempre presente e mai legata alla data di build -
aboutconsameAsper gli argomenti che corrispondono a un’entità - Versione Markdown delle pagine principali, servita come
text/markdowne connoindex - Accesso dei crawler AI verificato sul CDN, non solo nel
robots.txt - Controllo automatico del JSON-LD in CI
Domande frequenti
❓ Serve davvero un @id se ho già il JSON-LD in ogni pagina?
Il JSON-LD per pagina funziona per i rich result, ma senza un @id ogni pagina descrive la propria copia dell’autore e dell’editore. Con un @id stabile tutte le pagine parlano della stessa entità, e le piccole differenze tra un template e l’altro smettono di generare identità diverse.
❓ Le versioni Markdown creano contenuto duplicato?
Possono, se vengono indicizzate. Per questo conviene servirle con l’header X-Robots-Tag noindex: agenti e strumenti le possono leggere, ma i motori di ricerca non le mostrano nei risultati, e l’URL da citare resta quello della pagina HTML.
❓ Come controllo se un CDN blocca i crawler AI?
Fai una richiesta con lo user agent del crawler, per esempio GPTBot o ClaudeBot, e guarda il codice di risposta. Un 403 o una pagina di verifica indicano un blocco. Controlla poi le impostazioni sui bot del CDN, perché alcune regole bloccano i crawler verificati in base all’indirizzo IP, cosa che un test con user agent finto non può rilevare.
❓ Quale di questi errori conviene correggere per primo?
L’identità frammentata. Autore ed editore coerenti, con un @id stabile, sono la base su cui si appoggia tutto il resto, e la correzione di solito si fa in un solo punto del codice. L’escape del JSON-LD è altrettanto rapido e chiude un piccolo problema di sicurezza.
Conclusione
La GEO tecnica è soprattutto coerenza, e la coerenza si perde un template alla volta. Un sito con buone fondamenta può comunque presentare un autore diverso in ogni pagina, un escape dimenticato e un CDN che decide chi può leggerlo. Nessuna di queste correzioni è complessa: la parte difficile è accorgersene, ed è per questo che un audit periodico, fatto con la stessa checklist che si consiglia agli altri, vale più di qualsiasi ottimizzazione puntuale.
Riferimenti:

Co-Fondatore & CTO presso PAPION. Senior full-stack engineer specializzato in React, TypeScript, Node.js e sicurezza applicativa.