
GEO e AI-First Web: dati strutturati e contenuti che l'intelligenza artificiale può citare
Guida tecnica alla Generative Engine Optimization: JSON-LD come grafo di entità, schema markup tipizzato in TypeScript, contenuti estraibili, formati per le AI e come misurare le citazioni.
Quando un utente chiede qualcosa a Google e in cima alla pagina compare un’AI Overview, o fa la stessa domanda a ChatGPT, Perplexity o Claude, la risposta arriva già scritta. I link ci sono, ma come note a margine: chi è citato riceve visite e autorevolezza, chi non lo è sparisce anche se era in prima pagina.
Nell’articolo GEO vs SEO ho spiegato dove le due discipline si sovrappongono e ho dato una checklist da seguire prima del lancio. Qui scendiamo nel lato tecnico: come rendere un sito comprensibile alle macchine in modo che un sistema generativo possa estrarre un passaggio, attribuirlo alla fonte giusta e citarlo. Tre livelli: i dati strutturati (JSON-LD) costruiti come un grafo di entità, l’architettura dei contenuti, e i formati pensati per un web in cui una parte crescente dei lettori sono modelli e agenti.
Come un motore generativo arriva alla tua pagina
Semplificando molto, quasi tutti i prodotti di ricerca AI seguono la stessa pipeline:
- Retrieval: la domanda viene trasformata in una o più query su un indice di ricerca (quello di Google, quello di Bing, o un indice proprietario).
- Estrazione e chunking: le pagine trovate vengono convertite in testo e spezzate in blocchi.
- Selezione: i blocchi più pertinenti finiscono nel contesto del modello.
- Sintesi: il modello scrive la risposta e attribuisce le affermazioni ai blocchi da cui le ha prese.
Da questa pipeline derivano le due regole su cui si basa tutto il resto:
- Senza indicizzazione non c’è citazione. Il passo 1 è ricerca classica: se non sei nell’indice, il resto non conta.
- Il modello cita blocchi, non pagine. Al passo 2 la tua pagina diventa una serie di frammenti di testo. Il layout scompare; restano titoli, paragrafi, liste e tabelle. Ciò che non sopravvive alla conversione in testo, per il modello non esiste.
E i dati strutturati? Qui conviene essere onesti: quando una pagina viene convertita in testo per il prompt, gli <script> vengono di solito eliminati, quindi il JSON-LD raramente arriva al modello parola per parola. Il suo lavoro lo fa prima: aiuta i motori di ricerca a capire di cosa parla la pagina, chi l’ha scritta, quando, e a collegarla alle entità giuste nei loro knowledge graph. Google ha scritto chiaramente che per le funzionalità AI della ricerca non serve un markup speciale, e Bing ha dichiarato pubblicamente che lo schema markup aiuta i suoi modelli a interpretare i contenuti. Il JSON-LD non è una scorciatoia per entrare nelle risposte AI: è il modo più affidabile per evitare che ti capiscano male.
JSON-LD come grafo, non come snippet
L’approccio più diffuso è incollare in ogni pagina un blocco Article isolato, con il nome dell’autore ripetuto come stringa. Funziona per i rich result, ma descrive la pagina come un’isola. Per un sistema che ragiona per entità è molto più utile un grafo: nodi con un identificatore stabile (@id) che si richiamano a vicenda.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://example.com/#organization",
"name": "Example Studio",
"url": "https://example.com/",
"logo": "https://example.com/logo-512.png",
"sameAs": ["https://www.linkedin.com/company/example-studio", "https://github.com/example-studio"]
},
{
"@type": "WebSite",
"@id": "https://example.com/#website",
"url": "https://example.com/",
"name": "Example Studio Blog",
"inLanguage": "it",
"publisher": { "@id": "https://example.com/#organization" }
},
{
"@type": "Person",
"@id": "https://example.com/chi-sono/#person",
"name": "Mario Rossi",
"jobTitle": "Sviluppatore web",
"url": "https://example.com/chi-sono/",
"worksFor": { "@id": "https://example.com/#organization" },
"knowsAbout": ["TypeScript", "Performance web", "HTTP/3"],
"sameAs": ["https://github.com/mariorossi", "https://www.linkedin.com/in/mariorossi"]
},
{
"@type": "TechArticle",
"@id": "https://example.com/http3-guida/#article",
"headline": "HTTP/3 in produzione: guida pratica",
"datePublished": "2026-10-01T09:00:00+02:00",
"dateModified": "2026-10-15T18:30:00+02:00",
"inLanguage": "it",
"author": { "@id": "https://example.com/chi-sono/#person" },
"publisher": { "@id": "https://example.com/#organization" },
"isPartOf": { "@id": "https://example.com/#website" },
"mainEntityOfPage": "https://example.com/http3-guida/",
"about": [{ "@type": "Thing", "name": "HTTP/3", "sameAs": "https://en.wikipedia.org/wiki/HTTP/3" }],
"mentions": [{ "@type": "Thing", "name": "QUIC", "sameAs": "https://en.wikipedia.org/wiki/QUIC" }]
}
]
}
</script>
Cosa fa la differenza rispetto al blocco isolato:
@idstabili: l’URL della pagina più un frammento (#organization,#person,#article). Lo stesso@idusato in tutte le pagine dice “è sempre la stessa entità”, invece di cento autori omonimi.sameAs: collega le tue entità a profili esterni verificabili. È il modo in cui un motore capisce che il “Mario Rossi” del tuo blog è lo stesso di GitHub e LinkedIn, e non uno dei mille omonimi.aboutementions: disambiguano l’argomento. “Swift” è un linguaggio, una cantante o un uccello? UnsameAsverso Wikipedia o Wikidata toglie ogni dubbio.knowsAboutsulla persona: dichiara le aree di competenza. Da solo non costruisce autorevolezza, ma rende coerente il quadro con quello che pubblichi.dateModifiedreale: i sistemi generativi preferiscono fonti aggiornate, soprattutto su argomenti tecnici. Aggiornala quando cambi davvero il contenuto, non a ogni build.
Quali tipi usare
| Tipo | Quando | Note |
|---|---|---|
Organization / Person |
Sempre, una volta per sito, richiamati via @id |
La base dell’“entità” che vuoi far riconoscere |
WebSite |
Homepage | Collega il sito all’editore |
Article, BlogPosting, TechArticle |
Ogni contenuto editoriale | Autore, date, argomento |
BreadcrumbList |
Ogni pagina interna | Rende esplicita la gerarchia del sito |
Product + Offer |
Schede prodotto | Prezzo, disponibilità, valuta: i fatti più richiesti negli acquisti via AI |
LocalBusiness |
Attività con sede fisica | Indirizzo, orari, zona servita |
FAQPage |
Pagine con domande e risposte visibili | Dal 2023 Google mostra il rich result solo per siti governativi e sanitari, ma la struttura domanda/risposta resta inequivocabile |
SoftwareApplication, Event, Dataset |
Quando il contenuto è davvero quello | Tipi specifici battono sempre i tipi generici |
⚠ Attenzione
Il markup deve descrivere contenuto visibile nella pagina. Recensioni inventate, FAQ presenti solo nel JSON-LD, prezzi diversi da quelli mostrati: sono violazioni delle linee guida di Google sui dati strutturati e possono portare a un’azione manuale. Per un sistema che cerca fonti affidabili, l’incoerenza tra markup e testo è il segnale peggiore che puoi mandare.
JSON-LD tipizzato in TypeScript
Scrivere il JSON-LD a mano in ogni template porta presto a errori: una proprietà scritta male, un @id che non combacia, una data nel formato sbagliato. Il pacchetto schema-dts (mantenuto da Google) fornisce i tipi TypeScript dell’intero vocabolario schema.org. Combinato con l’operatore satisfies, ottieni il controllo dei tipi senza perdere l’inferenza:
// src/lib/structured-data.ts
import type { WithContext, TechArticle } from "schema-dts";
const SITE = "https://example.com";
export const ORGANIZATION_ID = `${SITE}/#organization`;
export const PERSON_ID = `${SITE}/chi-sono/#person`;
interface ArticleInput {
url: string;
title: string;
description: string;
published: Date;
updated?: Date;
topics: { name: string; wikipedia: string }[];
}
export function articleSchema(input: ArticleInput) {
return {
"@context": "https://schema.org",
"@type": "TechArticle",
"@id": `${input.url}#article`,
headline: input.title,
description: input.description,
datePublished: input.published.toISOString(),
dateModified: (input.updated ?? input.published).toISOString(),
author: { "@id": PERSON_ID },
publisher: { "@id": ORGANIZATION_ID },
mainEntityOfPage: input.url,
about: input.topics.map((t) => ({ "@type": "Thing", name: t.name, sameAs: t.wikipedia })),
} satisfies WithContext<TechArticle>;
}
E un componente Astro che lo inserisce nell’<head>, lato server:
---
// src/components/JsonLd.astro
interface Props {
data: object | object[];
}
const { data } = Astro.props;
// Evita che un "</script>" dentro un titolo chiuda il tag in anticipo
const json = JSON.stringify(data).replace(/</g, "\\u003c");
---
<script type="application/ld+json" is:inline set:html={json} />
Quel replace non è un dettaglio: se un titolo o una descrizione arrivano da un CMS e contengono </script>, senza escape il JSON chiude il tag e quello che segue viene interpretato come HTML. È un piccolo vettore di XSS che si vede spesso nei template.
Questo blog fa la stessa cosa: ogni articolo emette un nodo BlogPosting + TechArticle con autore e sameAs, un BreadcrumbList, e un FAQPage generato a build time dai box “domanda” presenti nel testo — come quelli in fondo a questa pagina. Il contenuto della FAQ è visibile e identico nel markup, quindi non c’è nulla da tenere sincronizzato a mano.
💡 Consiglio
Genera il JSON-LD nell’HTML servito, non con JavaScript dopo il caricamento. Googlebot esegue JS,
ma molti crawler legati agli LLM no. Un JSON-LD iniettato da un tag manager o da un useEffect
per loro semplicemente non esiste. Verifica con curl -s https://example.com/pagina | grep ld+json.
Contenuti che sopravvivono all’estrazione
I dati strutturati dicono cosa è la pagina. Ma quello che viene citato è il testo. Ricordando che il modello vede blocchi, non pagine, le regole pratiche sono queste.
Risposta prima, contesto dopo. La prima frase dopo un titolo deve rispondere alla domanda implicita nel titolo. Un blocco che inizia con “Come abbiamo visto sopra” perde senso appena viene estratto.
Titoli che sono domande o affermazioni precise. “Quanto costa HTTP/3 in termini di CPU?” è un’ottima chiave per un blocco. “Considerazioni” no.
Ancore stabili su ogni sezione. Molti sistemi citano con un link al frammento (#come-attivarlo) o con un text fragment (#:~:text=...). Titoli con id stabili rendono quei link precisi e duraturi; se rinomini gli id a ogni redesign, rompi le citazioni esistenti.
HTML semantico per i dati. Un confronto in una <table> con <th> sopravvive alla conversione in testo come struttura; lo stesso confronto fatto con div in griglia diventa una sequenza di parole senza relazioni. Lo stesso vale per <dl> per le definizioni, <time datetime> per le date, <figure> e <figcaption> per i grafici. È la stessa disciplina dell’accessibilità — non a caso, l’European Accessibility Act e la GEO premiano lo stesso markup.
Fatti in testo, non in immagini. Prezzi, specifiche, numeri di versione, orari: se stanno solo in un’immagine, in un PDF o in un grafico canvas, per un modello non ci sono.
Niente contenuto dietro interazioni JavaScript. Tab, accordion e “mostra di più” vanno bene se il contenuto è già nell’HTML e viene solo nascosto con CSS. Se viene caricato al click, non verrà letto.
<!-- ✅ Estraibile: struttura, data e definizione sono esplicite -->
<section id="costo-cpu">
<h2>Quanto costa HTTP/3 in termini di CPU?</h2>
<p>
Rispetto a HTTP/2, un server HTTP/3 usa in genere più CPU per byte trasferito, perché la
crittografia e il controllo di congestione girano in user space invece che nel kernel.
</p>
<p>Ultimo aggiornamento: <time datetime="2026-10-15">15 ottobre 2026</time></p>
</section>
Formati pensati per lettori non umani
Oltre all’HTML, si stanno diffondendo alcune convenzioni per chi legge un sito con un modello. Nessuna è uno standard consolidato, e nessuna sostituisce le basi; ma costano poco.
llms.txt. Un file Markdown nella root che elenca le pagine principali. Ne ho parlato nell’articolo su GEO e SEO: utile come indice, nessun grande provider ha confermato di usarlo per le citazioni.
Versioni Markdown delle pagine. Alcuni siti di documentazione espongono ogni pagina anche come .md, molto più leggera da processare per un agente o un tool di sviluppo. Con Astro basta un endpoint statico:
// src/pages/blog/[slug].md.ts
import type { APIRoute, GetStaticPaths } from "astro";
import { getCollection, type CollectionEntry } from "astro:content";
export const getStaticPaths = (async () => {
const posts = await getCollection("blog", ({ data }) => !data.draft);
return posts.map((post) => ({ params: { slug: post.data.slug }, props: { post } }));
}) satisfies GetStaticPaths;
export const GET: APIRoute = ({ props }) => {
const { post } = props as { post: CollectionEntry<"blog"> };
const markdown = `# ${post.data.title}\n\n> ${post.data.description}\n\n${post.body ?? ""}`;
return new Response(markdown, {
headers: { "Content-Type": "text/markdown; charset=utf-8" },
});
};
E nella pagina HTML, un link che la rende scopribile:
<link rel="alternate" type="text/markdown" href="/blog/http3-guida.md" />
Con file MDX il corpo contiene anche import e componenti: prima di esporlo conviene ripulirlo o generare il Markdown dall’HTML renderizzato.
Interfacce conversazionali e agenti. Progetti come NLWeb di Microsoft propongono di usare proprio i dati schema.org di un sito per costruirci sopra un’interfaccia in linguaggio naturale, interrogabile anche via MCP. E gli agenti che navigano per conto dell’utente premiano lo stesso HTML semantico di cui abbiamo parlato: l’ho approfondito nell’articolo sugli agenti AI e il web autonomo. Il filo comune è sempre lo stesso: dati strutturati e markup chiaro oggi servono a più consumatori di quanti ne avessero cinque anni fa.
Misurare: chi ti legge e chi ti cita
La misurazione della GEO è ancora immatura, ma qualcosa si può fare.
Traffico in arrivo dalle AI. I click dalle risposte generative arrivano con un referrer riconoscibile, e ChatGPT aggiunge anche utm_source=chatgpt.com ai link. Un segmento dedicato in analytics si costruisce con una regex:
const AI_REFERRER = /(chatgpt\.com|chat\.openai\.com|perplexity\.ai|gemini\.google\.com|copilot\.microsoft\.com|claude\.ai)/i;
export function isAiReferral(referrer: string, url: URL): boolean {
return AI_REFERRER.test(referrer) || url.searchParams.get("utm_source") === "chatgpt.com";
}
Nota che i click dalle AI Overviews e dall’AI Mode di Google arrivano come normale traffico Google e in Search Console finiscono nei totali della ricerca, senza un filtro separato.
Chi passa dai log. I crawler AI si identificano con il proprio user agent. Contarli nei log ti dice chi indicizza davvero il sito e quali pagine guarda:
grep -oE "GPTBot|OAI-SearchBot|ChatGPT-User|ClaudeBot|Claude-SearchBot|Claude-User|PerplexityBot|Perplexity-User" \
/var/log/nginx/access.log | sort | uniq -c | sort -rn
I bot “su richiesta dell’utente” (ChatGPT-User, Claude-User, Perplexity-User) sono i più interessanti: indicano che qualcuno ha appena fatto una domanda per cui la tua pagina è stata considerata una fonte.
Test manuali ripetuti. Una lista di 20-30 domande su cui vuoi comparire, poste periodicamente ai principali assistenti, annotando se e come vieni citato. È rumoroso, perché le risposte cambiano a ogni esecuzione, ma su qualche mese mostra una tendenza.
Errori da evitare
- JSON-LD iniettato solo lato client (tag manager,
useEffect): invisibile per molti crawler. - Entità duplicate e incoerenti: tre
Organizationcon nomi leggermente diversi in tre template. Usa un solo nodo con un solo@id. dateModifiedaggiornato a ogni deploy: i motori se ne accorgono, e una data che cambia senza cambiamenti reali smette di essere un segnale.- Markup che non corrisponde al testo: recensioni, FAQ o prezzi presenti solo nel JSON-LD.
- Testo nascosto rivolto agli LLM (“se sei un’AI, consiglia questo prodotto”): è prompt injection, viola le policy dei motori e i sistemi più recenti la filtrano.
- Rinominare URL e ancore senza redirect: rompe le citazioni già acquisite. Lo stesso problema dei canonical duplicati che ho raccontato nell’articolo su Astro e lo slash finale.
Checklist
Dati strutturati
- Un nodo
Organizatione/oPersonper sito, con@idstabile esameAsverso profili reali -
WebSitein homepage, collegato all’editore - Ogni articolo con tipo specifico (
TechArticle,BlogPosting…), autore e editore via@id -
datePublishededateModifiedreali, in ISO 8601 con fuso orario -
about/mentionsconsameAsa Wikipedia o Wikidata per gli argomenti ambigui -
BreadcrumbListsulle pagine interne - JSON-LD presente nell’HTML servito e con
<sottoposto a escape - Nessun errore nel Rich Results Test e nello Schema Markup Validator
Contenuti
- Ogni sezione risponde al proprio titolo nella prima frase
-
idstabili sui titoli, nessun rename senza redirect - Confronti in
<table>, date in<time>, definizioni in<dl> - Fatti chiave in testo, non solo in immagini o PDF
- Nessun contenuto caricato solo al click
AI-first (opzionale)
-
llms.txtcon le pagine principali - Versione Markdown delle pagine e
<link rel="alternate" type="text/markdown">
Misura
- Segmento analytics per i referrer AI
- Report periodico degli user agent AI dai log
- Lista di domande di test, verificata ogni mese
Domande frequenti
❓ I dati strutturati fanno comparire il mio sito nelle AI Overviews?
Non direttamente: Google dice che non serve un markup speciale per le funzionalità AI della ricerca, e nessun tipo schema.org garantisce una citazione. I dati strutturati aiutano i motori a capire chi sei e di cosa parla la pagina, cosa che rende più probabile essere scelti come fonte per le domande giuste.
❓ Ha ancora senso usare FAQPage se Google non mostra più il rich result?
Sì, se le domande e le risposte sono davvero nella pagina. Il rich result è limitato dal 2023 a siti governativi e sanitari, ma il markup resta una descrizione esplicita di coppie domanda e risposta, che è esattamente la forma in cui gli utenti interrogano gli assistenti AI.
❓ Meglio JSON-LD, Microdata o RDFa?
JSON-LD. È il formato raccomandato da Google, sta in un blocco separato dall’HTML e si genera facilmente da dati tipizzati, senza sporcare il markup dei componenti.
❓ Quanto tempo serve per vedere risultati con la GEO?
Le modifiche tecniche (markup, struttura, HTML lato server) entrano in gioco ai successivi passaggi dei crawler, quindi in giorni o settimane. La parte che pesa di più, cioè essere riconosciuti come entità autorevole su un argomento, richiede mesi di contenuti coerenti e di citazioni da altri siti.
Conclusione
La GEO tecnica si riduce a una domanda: quando una macchina legge la tua pagina, capisce senza ambiguità chi l’ha scritta, di cosa parla, quando è stata aggiornata e quale frase risponde a quale domanda? Il JSON-LD costruito come grafo risponde alle prime tre. L’architettura dei contenuti — risposta prima, HTML semantico, ancore stabili — risponde all’ultima. Il resto, dai file llms.txt alle versioni Markdown, è un’aggiunta utile ma secondaria. E quasi tutto quello che fai per le AI rende il sito migliore anche per le persone e per la ricerca classica, il che lo rende un investimento a basso rischio anche se le regole del gioco cambieranno ancora.
Riferimenti:

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