# 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.

- Autore / Author: Diego Betto (https://diegobetto.com/about/)
- Pubblicato / Published: 2026-10-29
- URL: https://diegobetto.com/audit-geo-errori-comuni/
- Tag: geo, seo, json-ld, ai

Una checklist GEO è facile da scrivere e difficile da rispettare. Negli articoli su [cos'è la GEO](https://diegobetto.com/cos-e-la-geo-generative-engine-optimization/), [GEO vs SEO](https://diegobetto.com/geo-vs-seo-checklist/) e [dati strutturati e AI-first web](https://diegobetto.com/geo-ai-first-web-dati-strutturati/) 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:

```json
{
  "@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.

```ts
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à:

```json
"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:

```html
<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: noindex` evita 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**.

```bash
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:

```txt
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 `@id` deve 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 `@id` stabile, richiamato da tutte le pagine
- [ ] Editore coerente con la realtà, lo stesso in tutto il sito
- [ ] JSON-LD con `<` sottoposto a escape
- [ ] `dateModified` sempre presente e mai legata alla data di build
- [ ] `about` con `sameAs` per gli argomenti che corrispondono a un'entità
- [ ] Versione Markdown delle pagine principali, servita come `text/markdown` e con `noindex`
- [ ] 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:**

- [schema.org — Data model](https://schema.org/docs/datamodel.html)
- [Google Search Central — Article structured data](https://developers.google.com/search/docs/appearance/structured-data/article)
- [llms.txt — proposta e specifica](https://llmstxt.org/)
- [Google — Common crawlers e Google-Extended](https://developers.google.com/search/docs/crawling-indexing/google-common-crawlers)
