Diagnosing Semantic Disconnect for AI Overviews & RAG Search
Last month a client came to me with a problem that didn't make sense on paper. Their product pages were losing impressions in Search Console month over month, but the position tracking for their main keywords hadn't moved. Page speed was fine. Core Web Vitals passed. Rich Results Test came back clean, no warnings anywhere. So I did what you'd do — I typed the exact query into Google myself.
The AI Overview didn't mention them at all. It pulled its answer from a competitor ranked two spots lower, quoting a fact that was, word for word, sitting on my client's own page.
That's not an indexing bug. It's not a content quality problem in the way we used to think about content quality. It's what I've started calling a semantic disconnect — the retrieval system behind AI Overviews can see your page exists, but it can't confidently stitch your entities together into something it trusts enough to cite. The content is there. The machine just can't connect the dots.
Here's how I actually go about diagnosing and fixing this, based on the audits I've run over the past few months.
Why Your Knowledge Graph Might Be Fragmented Without You Knowing It
RAG-powered AI Overviews don't read a page the way a person does. They extract entities, pull out their attributes, and try to figure out how those entities relate to each other and to everything else the model already knows. A semantic disconnect usually comes down to one thing: your site is describing the same entity in slightly different, disconnected ways across different pages, and the system can't tell they're the same thing.
I start every audit the same way — mapping out the core entities a site actually talks about, then checking how consistently and densely they're represented. Google's Natural Language API is useful here, and so is a content gap tool like Ahrefs, though I use it specifically to look for entity overlap rather than keyword overlap. Then I go into Screaming Frog and pull custom XPath extractions across the page clusters that matter most — product names, attributes, whatever the site's core "things" are.
If your product names live in something like and an MPN sits in a meta tag, the extraction rules look like this:
// XPath for product name
//span[@class="product-name"]/text()
// Regex for MPN in meta tag
Cross-reference that against Search Console's Performance data, specifically filtering for queries that should be triggering an AI Overview citation and aren't. What you usually find is that individual pages rank fine, but the collective authority around the bigger concept is thin or inconsistent. Every entity needs to be identified the same way everywhere it appears, and that consistency has to work on two separate layers. One is ordinary internal linking — real anchor text pointing between pages, the thing HTML has always done. The other is Schema-level: @id and @reverse properties that describe how entities relate to each other inside your structured data, independent of whether a visible link exists on the page at all. A product page shouldn't just link to its category in the nav. It should also declare, explicitly in the JSON-LD, its relationship to the manufacturer, related accessories, and review sources — even if no visible anchor text covers that relationship anywhere on the page.
Building Relationships Into Your Schema, Not Just Declaring Entities
Most sites that implement Schema.org stop at declaring things exist. Product here, Organization there, done. Few actually define how those things relate to each other using @id and @reverse inside a single @graph.
Take a product page for "Widget X," made by "Acme Corp." The lazy version declares Product and Organization as two separate, disconnected blocks. The version that actually helps a RAG system looks like this:
{
"@context": "https://schema.org",
"@graph": [
{
"@id": "https://www.example.com/products/widget-x/#product",
"@type": "Product",
"name": "Widget X",
"description": "The latest innovation in widgets.",
"brand": { "@id": "https://www.example.com/#acme-corp" },
"manufacturer": { "@id": "https://www.example.com/#acme-corp" },
"offers": {
"@type": "Offer",
"price": "199.99",
"priceCurrency": "USD"
}
},
{
"@id": "https://www.example.com/#acme-corp",
"@type": "Organization",
"name": "Acme Corp.",
"url": "https://www.example.com/about-us/",
"logo": "https://www.example.com/images/acme-logo.png",
"@reverse": {
"brand": { "@id": "https://www.example.com/products/widget-x/#product" },
"manufacturer": { "@id": "https://www.example.com/products/widget-x/#product" }
}
}
]
}
The @id gives each entity a stable identifier you can reference from anywhere else in the graph. The @reverse keyword is actually defined in the JSON-LD specification itself, not by Schema.org — Schema.org just adopts it. On Acme Corp, it explicitly says "this company manufactures and brands Widget X," from the company's side rather than the product's. That bidirectional link matters more than people assume, and it's the kind of structuring Google's own guidance on structured data recommends when you're describing more than one related entity on a page. The real risk with scattering Schema across multiple disconnected blocks on the same page isn't that a crawler can't technically read all of them — it will extract every valid JSON-LD block it finds. The risk is that disconnected blocks are more likely to declare conflicting or duplicate information about the same entity without anyone noticing, since nothing forces them to reference each other. Consolidating into one @graph per page — sometimes per site, for shared entities like the main Organization — makes those conflicts far easier to catch before they ever reach a crawler.
Content Gaps Aren't About Missing Keywords Anymore
This is the part people get wrong most often. They think a content gap means a missing keyword. For AI Overviews, it almost never does. It means missing topical depth — the AI Overview is trying to synthesize an answer from multiple angles at once, and your site only covers one of them.
Say someone searches "best noise-canceling headphones for travel." The Overview needs to pull together sound quality, battery life, comfort, portability, and travel-specific features. If your headphones product page only talks about audio fidelity, while a separate blog post covers travel gear and yet another review compares battery life across gadgets, you've made it genuinely hard for the system to connect those pieces and attribute a coherent answer back to your domain.
The fix is building actual topical clusters, not just internal links for their own sake:
- Pillar pages that cover the full breadth of a topic at a reasonable level of depth.
- Satellite pages that go deep on specific sub-entities — a dedicated page on active noise cancellation technology, for instance, rather than a paragraph buried in a product description.
- Internal links with real anchor text, and canonical tags pointing to the single authoritative version of any entity that shows up on multiple pages.
- Regular checks of your canonical chain — Screaming Frog's canonical report catches the cases where signals get fragmented across near-duplicate URLs without anyone noticing.
Anecdotally, we run this kind of scoring internally through a metric we built called Veridex Score — it's not an industry-standard metric, just our own way of quantifying how coherently a page declares its entities and relationships. Pages that score well on it end up showing in AI Overviews far more often than pages that just have good keyword density. Whatever tool you use to measure this, the correlation between entity coherence and citation frequency is the thing worth tracking.
Crawl Budget Still Matters, Even for AI Overviews
None of the schema work matters if the pages carrying your best entity data aren't getting crawled often enough. RAG systems want fresh, current information, and crawl budget isn't just "did Googlebot visit the page" — it's whether the right pages, the ones with new or updated entity data, get prioritized.
The usual suspects when I'm digging through crawl logs:
- Parameter bloat. URLs like
?sessionid=abc&sort=price&filter=colorcreate duplicate content that eats crawl equity for no reason. Canonical tags help; server-side URL rewriting helps more. - Deep pagination. Infinite scroll and heavily paginated category pages bury the product detail pages that actually matter. A flatter architecture with clean sitemaps beats relying on
rel="next"/rel="prev", which Google mostly ignores for indexing purposes now anyway. - JavaScript rendering delays. JSON-LD isn't a visual element, so it doesn't factor into First Contentful Paint or Largest Contentful Paint the way an image or heading does — but that's exactly why it's easy to overlook. What actually matters is whether the Schema block exists in the raw server-rendered HTML, before any client-side JavaScript runs. Pull the page with
curland check the raw response; if your JSON-LD only shows up after hydration, you're relying on a secondary render pass the crawler may never fully complete. I still see teams defer Schema injection until after the "real" content loads — that's backwards, and it has nothing to do with paint timing.
This isn't an official Google feature, but a pattern I've used on sites with a lot of dynamic content: maintain a small, separate sitemap that lists only pages whose entity data changed recently, alongside your regular sitemap. It's just a way to make sure your lastmod values on those specific URLs stay meaningfully accurate rather than getting lost in a sitemap of thousands of mostly-static pages. Pair that with correct Last-Modified and ETag headers — they're doing more communication work than most people give them credit for.
Three Failure Patterns I Keep Running Into
1. The site can't tell "Apple" the company from "Apple" the fruit
This sounds almost too basic to matter, but entity collisions happen constantly, and they're subtle. A product page for "Apple Watch Series 9" uses "Apple Watch" loosely across the site, sometimes referring to the company, sometimes the product line, with no explicit @id tying any of it down.
The broken version:
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Apple Watch Series 9",
"description": "The latest Apple Watch.",
"brand": { "@type": "Organization", "name": "Apple" }
}
The fix isn't complicated — give every distinct entity its own @id, link them inside a @graph, and for anything globally recognizable, add a sameAs pointing to Wikidata or Wikipedia:
{
"@context": "https://schema.org",
"@graph": [
{
"@id": "https://www.example.com/products/apple-watch-series-9/#product",
"@type": "Product",
"name": "Apple Watch Series 9",
"description": "The latest smartwatch from Apple Inc.",
"brand": { "@id": "https://www.example.com/#apple-inc" },
"manufacturer": { "@id": "https://www.example.com/#apple-inc" },
"sameAs": [
"https://en.wikipedia.org/wiki/Apple_Watch_Series_9",
"https://www.wikidata.org/wiki/Q122557404"
]
},
{
"@id": "https://www.example.com/#apple-inc",
"@type": "Organization",
"name": "Apple Inc.",
"url": "https://www.apple.com/",
"logo": "https://www.example.com/images/apple-logo.png",
"sameAs": "https://en.wikipedia.org/wiki/Apple_Inc."
}
]
}
Run it through the Rich Results Test and the Schema.org Validator, sure — but the real test is searching for the ambiguous term yourself and watching what the AI Overview actually does with it.
2. The Schema gets cut off before it's fully rendered
A large product page with 200+ variations, each with its own Offer block, hits a server-side character limit or a client-side script fails silently partway through injection. What ends up in the DOM is a truncated, malformed JSON block:
There's no shortcut here — your rendering pipeline has to handle the full payload, or you need an API-driven approach where the core entity is always present and auxiliary data (like the long tail of offers) is linked rather than inlined. Pull the raw HTML with curl -A "Googlebot" [your-url] and compare it against what actually renders in the browser. The Coverage tab in DevTools will show you unused JS that might be slowing injection down, too.
3. SSR and CSR are fighting each other
Hybrid rendering setups sometimes have the server ship a minimal Schema block, expecting client-side JavaScript to "hydrate" it with the full data — reviews, complete offers, whatever. When that hydration call fails or times out, you're left with an incomplete entity graph and no fallback.
The rule I give clients: whatever you serve server-side has to be the complete, canonical version. Client-side JavaScript should only ever enhance it, never carry core entity declarations on its own. If dynamic pricing updates the DOM later, fine — but the initial SSR payload needs everything a crawler would need even if that later update never fires. Compare what Search Console's URL Inspection tool shows as "crawled" against what you see in DevTools after a full page load; any gap between the two is your hydration race condition.
A Quick Comparison of the Approaches
| Strategy | Setup Effort | Crawl Reliability | Semantic Coherence | Ongoing Maintenance |
|---|---|---|---|---|
| Basic page-level Schema | Low | Moderate, prone to truncation | Low, fragmented | Low |
| Advanced @graph with entity @ids | Moderate-High | High | High | Moderate-High |
| Topical cluster content architecture | High | High | High | High, ongoing |
| SSR with full Schema payload | Moderate | Very high | High | Moderate |
| Client-side Schema injection | Low | Low-moderate | Moderate, race-condition prone | Moderate |
Frequently Asked Questions
How long before Google's AI Overviews reflect a Schema change I just made?
There's no official timeline from Google on this — I haven't seen them commit to a number anywhere. In practice, on high-priority pages I've worked on, changes tend to show up within days to a few weeks, tied more to how often the page gets recrawled than to any fixed schedule.
It's also worth being honest about something bigger here: Google's own documentation on AI features states there are no special optimizations or extra schema requirements needed to appear in AI Overviews — the same fundamental SEO practices that apply to classic Search apply here too. Everything in this piece about @graph structuring and entity disambiguation is still worth doing, because clearer structured data measurably helps Google's structured data systems parse your content correctly — but treat it as good technical hygiene that removes ambiguity, not as a documented requirement or guaranteed lever for AI Overview citations specifically.
Should I put everything into one giant @graph per page?
Generally yes — it makes the @id / @reverse relationships explicit and easier for search engines to parse as a whole. On very large sites with thousands of sub-entities, you'll want some pragmatic limits, but the core entities and their immediate relationships should always be present in full.
My Rich Results test passes clean, but AI Overviews still ignore me. Why?
Because passing validation only proves your Schema is syntactically correct — it says nothing about whether the system actually understands it well enough to trust it for retrieval. That's a different bar. It comes down to disambiguation, topical breadth, and how well your entities link to each other, not whether the JSON parses.
Is there such a thing as "too much" Schema?
Not a penalty, no. JSON-LD is part of the HTML response, not a separate crawl request, so it doesn't cost you crawl budget the way an extra image or API call would. The real cost is bloated payload size slowing down parsing, and diluted signal — redundant or irrelevant markup makes it harder for the system to tell which entities actually matter on the page. Every piece of Schema should map to something visible and true on the page, not just be there because a generator produced it.
Checking your own site's entity signals: Our free SEO audit tools can help surface where entity signals break down before you start rewriting Schema by hand.