Most structured data audits end the same way: the validator says zero errors, the Rich Results Test passes, and six months later the site has still never appeared in a single ChatGPT or Perplexity answer.
I have gone through this with enough sites now to say it plainly — the problem is almost never syntax. It is that the markup describes a page when the retrieval system is looking for an entity, and the page's actual content never reaches the crawler in the first place.
Here are the seven failures I see repeatedly, in the order they cost you the most.
The default pattern is a single WebPage block. That tells a machine "this is a page of the type WebPage" — which is roughly as useful as telling it "this is a file."
Retrieval and grounding systems need to resolve who you are, not what tag wraps the URL. There is no path from WebPage to a knowledge graph node.
Before
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "Acme Analytics — Product Page",
"url": "https://acme.dev/product"
}
After
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://acme.dev/#organization",
"name": "Acme Analytics",
"url": "https://acme.dev/",
"logo": {
"@type": "ImageObject",
"url": "https://acme.dev/logo-512.png"
},
"sameAs": [
"https://github.com/acme-analytics",
"https://www.crunchbase.com/organization/acme-analytics",
"https://en.wikipedia.org/wiki/Acme_Analytics"
]
},
{
"@type": "SoftwareApplication",
"@id": "https://acme.dev/product#app",
"name": "Acme Analytics",
"applicationCategory": "BusinessApplication",
"operatingSystem": "Web",
"publisher": { "@id": "https://acme.dev/#organization" },
"description": "Privacy-first web analytics with cookieless session tracking.",
"url": "https://acme.dev/product"
},
{
"@type": "WebPage",
"@id": "https://acme.dev/product#webpage",
"url": "https://acme.dev/product",
"isPartOf": { "@id": "https://acme.dev/#website" },
"about": { "@id": "https://acme.dev/product#app" }
}
]
}
The @id values are the actual work here. They turn three disconnected blocks into one addressable graph, so any node can be referenced from anywhere on the site without repeating the data.
sameAs, or sameAs is the cheapest disambiguation signal that exists and it is the one most often skipped.
An LLM asked "what is Acme Analytics" has no way to know your domain is the authority on Acme Analytics. It does know what a GitHub org, a Crunchbase entry, and a Wikidata item are. sameAs is how you hand it the bridge.
Rules that matter here:
This is the quiet killer on Next.js, Nuxt, React SPAs, and anything using a head-management library.
If this is how your schema gets onto the page:
// ❌ invisible to most AI crawlers
useEffect(() => {
const script = document.createElement('script');
script.type = 'application/ld+json';
script.text = JSON.stringify(schema);
document.head.appendChild(script);
}, [schema]);
...then the crawler has to execute JavaScript and wait for hydration and wait for your effect to flush before the markup exists. Googlebot will sometimes manage it. Most AI crawlers will not.
The fix is boring: serialize the JSON server-side and render it as static text in the document.
// ✅ present in the raw HTML response
const jsonLd = JSON.stringify(schema, null, 2);
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: jsonLd }}
/>
);
Verify it in the only way that counts — not in DevTools, but in the actual response:
curl -s https://acme.dev/product | grep -c 'application/ld+json'
If that returns 0 while the browser shows valid schema, you've found your bug.
Same root cause as #3, much worse consequence. Schema is metadata. Content is what gets embedded and cited.
If your product copy, documentation body, or article text renders client-side, you are shipping an empty shell to every crawler that doesn't run a browser.
curl -s -A "GPTBot" https://acme.dev/docs/getting-started | wc -w
curl -s https://acme.dev/docs/getting-started | wc -w
A 30-word difference is a widget. A 900-word difference means your documentation does not exist as far as retrieval is concerned — no matter how good your JSON-LD is.
The practical answer is SSR, SSG, or ISR for every route you want cited. Client-rendered routes are for app state, not published content.
Validators check shape, not meaning. These all pass and all degrade the output:
| Value | Broken | Fixed |
|---|---|---|
datePublished |
"Oct 24, 2026" |
"2026-10-24" (ISO 8601) |
image |
"/cover.png" |
"https://acme.dev/cover.png" |
@type |
"Webpage" |
"WebPage" |
@type |
"Article" with trailing space |
"Article" |
name |
`"Acme Analytics | Best Tools"` |
description |
400-character paragraph | 150–160 chars, one sentence |
ISO dates matter more than they look. "Oct 24, 2026" is ambiguous enough that some parsers drop the field entirely, and dateModified is one of the few freshness signals you fully control.
FAQPage used as a keyword dump
Two separate mistakes get made here constantly.
Mistake A — malformed structure. mainEntity must be an array of Question objects, each with a nested acceptedAnswer of type Answer. A flat {"question": "...", "answer": "..."} is not Schema.org.
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Does JSON-LD improve AI search visibility?",
"acceptedAnswer": {
"@type": "Answer",
"text": "JSON-LD does not directly improve rankings, but it removes ambiguity about what a page and its publisher are, which helps retrieval systems select the page as a source."
}
}
]
}
Mistake B — mismatched pairs. The question in the markup has to be answered by the text that follows it on the page. If the marked-up question says "How much does it cost?" and the visible answer is about onboarding, you have created a false pair. That is worse than no markup, because now the system has a confident wrong answer attributed to you.
One honest caveat: Google restricted FAQ rich results to authoritative government and health sites back in 2023, and removed HowTo rich results entirely. So this markup will not give you a SERP feature. What it still does is produce clean, self-contained question-answer chunks that retrieval pipelines can pull without parsing your surrounding prose. That's the reason to keep it — not the rich result.
Bolt on a plugin, add a second one later, and you end up with two Organization blocks per page saying different things:
{ "@type": "Organization", "name": "Acme Ltd" }
{ "@type": "Organization", "name": "Acme Analytics Inc.", "url": "https://www.acme.dev" }
To a reconciling system this is two companies. Consolidation fails silently and you get no error anywhere.
Pick canonical values once — exact legal name, exact canonical URL, exact logo — and use them in <title>, og:title, and every Organization node. Then dedupe:
curl -s https://acme.dev/ | grep -o '"@type":"Organization"' | wc -l
If you only do five things after reading this:
curl your three most important URLs and confirm the body copy is there without JSapplication/ld+json blocks in the raw HTML — zero means everything else is mootdateModified is ISO 8601 and actually recent"@type":"Organization" and make sure it appears once, with consistent values
Notice that none of these involve the Rich Results Test. It tells you the markup is well-formed, which was never the question.
Does adding JSON-LD get my site cited by ChatGPT?
Not on its own. Citation depends on whether the page is crawlable, whether its content is retrievable without JavaScript, and whether it is the clearest available answer to the question. Schema removes ambiguity about the publisher and the entity; it does not create content authority.
Should I block GPTBot?
GPTBot is OpenAI's training crawler. It is separate from OAI-SearchBot, which handles ChatGPT search, and ChatGPT-User, which fires when a user asks ChatGPT to fetch a URL. Blocking GPTBot does not remove you from ChatGPT search results. Blocking OAI-SearchBot does. Make that decision deliberately rather than copying a robots.txt snippet from a blog post.
Does FAQPage markup still do anything?
Not for rich results on most sites. It still produces clean question-answer pairs that are trivial to chunk during retrieval, which is a real but modest benefit.
How long until structured data shows an effect?
Recrawl cadence is the limiter, not the markup. Crawlers revisit popular pages within days and low-traffic pages within weeks to months. Expect a quarter, not a week, and track server-log bot hits rather than trying to infer it from traffic.
If you want the full version of this audit as a single document — all seven checks expanded into a 100-point readiness list, plus the complete JSON-LD templates for Organization, SoftwareApplication, Article, Person, and FAQPage graphs, already wired with @id cross-references — I put it together here: Structured for Answers →
Next in this series: How to Test If AI Crawlers Can Actually Read Your Site — the eight checks that tell you where you actually stand before you change a single line of markup.