JavaScript Schema and AI Crawlers: Is Your JSON-LD Actually Retrieved?

by

·

Diagram of the JavaScript schema AI crawlers retrieval path: raw HTML fetch, render pass, and JSON-LD parsing

Short version: if JavaScript writes your structured data into the page after load, most AI crawlers never see it. That is the JavaScript schema AI crawlers problem in one sentence—and it quietly breaks structured data on a large share of modern sites. GPTBot, ClaudeBot, and PerplexityBot fetch your raw HTML and move on. They do not run your scripts, so any JSON-LD added by a tag manager, a useEffect hook, or a hydration step simply isn't there when they read the page.

This matters because structured data is one of the clearest ways to tell an answer engine what your page is, who published it, and how it connects to your brand. If that signal exists only in the rendered DOM, you are optimizing for a reader that never arrives. Below: how to check what is actually retrieved, why tag managers and hydration fail, and how to fix it—grounded in crawler-traffic data and a controlled test you can reproduce.

Diagram of the JavaScript schema AI crawlers retrieval path: raw HTML fetch, render pass, and JSON-LD parsing

The short answer: most AI crawlers can't see JavaScript-injected schema

Most AI crawlers do not execute JavaScript. They read the HTML your server returns and nothing more. If your JSON-LD is added client-side, it is absent from what they retrieve—so it cannot inform how ChatGPT, Claude, or Perplexity describe your brand.

Vercel's analysis of AI crawler traffic found that none of the major AI crawlers—OpenAI's GPTBot, OAI-SearchBot, and ChatGPT-User; Anthropic's ClaudeBot; and PerplexityBot—render JavaScript. They sometimes fetch script files (GPTBot in 11.50% of requests, ClaudeBot in 23.84%) but never execute them, so they can't read client-rendered content. The meaningful exception is Google: Gemini and Google's AI surfaces lean on Googlebot's infrastructure, which renders JavaScript on a deferred pass.

So the rule of thumb is blunt. For everything except Google's stack, treat AI crawlers as non-rendering. Whatever isn't in the raw HTML doesn't exist to them.

What "retrieved" actually means: the three gates your JSON-LD must pass

"Retrieved" is not one step—it is three. For structured data to help an AI engine, it must clear three gates: Retrieval (present in the raw bytes), Rendering (built into the DOM), and Reconciliation (valid and parseable). JavaScript-injected schema often clears the last two but fails the first—which is exactly why it looks fine in a browser and vanishes for crawlers.

This three-gate model is the fastest way to reason about a specific bot. A non-rendering crawler can only ever clear Gate 1. Googlebot can clear all three—but on a delay, and never guaranteed for every URL. Design so Gate 1 always passes, and every downstream engine works.

Gate 1 — Retrieval: is it in the raw bytes?

Retrieval is whether the <script type="application/ld+json"> block is physically present in the HTTP response body, before any JavaScript runs. This is the only gate that non-rendering crawlers clear. If your schema isn't in these bytes, GPTBot, ClaudeBot, and PerplexityBot see nothing.

Gate 2 — Rendering: is it in the DOM after JS runs?

Rendering is whether the block appears once scripts execute and the DOM is built. A tag-manager tag or a hydration effect that injects JSON-LD passes here—which is why it looks correct in your browser's Elements panel and misleads teams into thinking the job is done.

Gate 3 — Reconciliation: is it valid and parseable?

Reconciliation is whether the JSON is well-formed, uses correct @type values, and references entities that resolve. A block can render perfectly and still fail here—duplicate @ids, a trailing comma, or a hydration mismatch that ships two conflicting Article objects. Clearing Gate 1 is wasted if Gate 3 breaks.

Which AI crawlers render JavaScript? A decision matrix

Here is how the major crawlers behave, and whether client-side JSON-LD reaches them. Use it to decide how much you can rely on JavaScript for a given surface.

Crawler Operator Renders JavaScript? Sees JS-injected JSON-LD?
GPTBot OpenAI No No
OAI-SearchBot / ChatGPT-User OpenAI No No
ClaudeBot Anthropic No No
PerplexityBot Perplexity No No
Googlebot (feeds AI Overviews, AI Mode, Gemini grounding) Google Yes (deferred render) Eventually, after render
Bingbot (feeds Microsoft Copilot) Microsoft Limited Unreliable

Two nuances matter. Google rendering is not a safety net. It runs on a second, delayed pass subject to crawl budget, and the engines that build their own answers—ChatGPT, Claude, Perplexity—largely rely on their own non-rendering crawlers, not Googlebot. If you want to see which index each assistant actually draws from, our breakdown of which search index powers each AI engine maps it engine by engine. Treat any crawler not proven to render as if it can't.

How to test whether your JSON-LD is actually retrieved

The fastest test is to fetch your page the way a non-rendering crawler does and search the raw response for your schema. If it isn't there, AI crawlers can't see it. You need no special access—just a terminal.

  1. Fetch the raw HTML. Run curl -sL https://example.com/your-page | grep -i "application/ld+json". Servers return the same bytes to any client, so plain curl already shows what a non-rendering bot receives.
  2. Confirm with a crawler user agent. Repeat with curl -A "GPTBot" -sL ... to rule out user-agent-specific delivery. If the result differs, you are cloaking by UA—fix that separately.
  3. Read the block, not just its presence. Use grep -A 30 "application/ld+json" to print the JSON and eyeball whether it is complete or truncated.
  4. Render the page and compare. Load the same URL in Google's Rich Results Test in URL mode, which renders like Googlebot. Schema that appears here but not in step 1 is JavaScript-injected—visible to Google eventually, invisible to non-rendering AI crawlers now.
  5. Validate the markup. Paste the block into the Schema.org validator to clear Gate 3.

If step 1 is empty and step 4 shows the schema, the diagnosis is confirmed: your structured data lives only in the DOM. For a deeper, repeatable version of this method across content types, see our repeatable test for whether AI crawlers render JavaScript.

Terminal output comparing curl with a GPTBot user agent against a rendered DOM snapshot for a JSON-LD block

What our controlled test found

To make the failure concrete, we built one article page in Next.js and injected the same Article JSON-LD three ways: hardcoded in the server HTML, added via a client-side useEffect hook, and pushed through a Google Tag Manager custom-HTML tag. We then fetched the URL with a non-rendering user agent and compared it against the Googlebot-rendered DOM.

Injection method In raw HTML (non-rendering crawler)? In rendered DOM (Googlebot)?
Hardcoded in server HTML Yes Yes
useEffect client hook No Yes
GTM custom-HTML tag No Yes (URL mode only)

Only the server-rendered variant appeared in the raw bytes. Both JavaScript methods were invisible to the non-rendering fetch and surfaced only once the page was rendered. The takeaway is unambiguous: for retrieval, injection method—not markup quality—decides whether AI crawlers ever see your schema. This holds for every schema type—Organization, Product, FAQPage, BreadcrumbList—because retrieval depends on where the markup lives, not on what @type it declares.

Tag manager failure modes

Google Tag Manager is the most common way JSON-LD goes missing. GTM is a JavaScript container that fires after the page loads, so anything it injects lives only in the rendered DOM. Non-rendering AI crawlers receive the HTML before GTM runs and never see the tag.

The failure has several flavors worth naming:

  • Custom-HTML tag timing. The tag fires on DOM Ready or a later trigger—well after the initial byte stream a crawler reads.
  • Consent-mode gating. If schema injection waits for a consent event that a bot never triggers, the tag may not fire at all.
  • Debug-only success. GTM Preview and the Rich Results Test URL mode both render, so the block looks present. That rendered view masks the raw-HTML reality a non-rendering crawler actually reads.

Google's own documentation on generating structured data with JavaScript confirms Googlebot can process JS-generated markup, but still recommends placing JSON-LD in the source. For AI visibility, "Google can eventually render it" is the wrong bar—the assistants your buyers use don't render at all.

Hydration and framework failure modes

Client-side frameworks introduce a subtler class of failures where the schema is in your code but not in the response. These are easy to miss because everything works in development and in the browser.

Common patterns we see when auditing structured data for AI search:

  • useEffect injection. Adding the <script> inside an effect guarantees it is client-only. It never reaches the server-rendered HTML.
  • Streaming and Suspense flush order. In the Next.js App Router, schema placed inside a Suspense boundary or a streamed segment can flush after the initial HTML—so a non-rendering crawler reads the shell without it. The markup is valid; it just arrives too late for a bot that never waits.
  • Hydration mismatches. If the server and client render different markup, React can discard the server node during hydration, quietly removing schema that was in the raw bytes.
  • Double injection. A component that mounts twice (or a layout plus a page both emitting Article) ships conflicting entities, failing Gate 3.

The through-line: framework convenience often trades away retrieval. If schema depends on the client to exist, it is a client-only signal. The same visibility trap affects other JS-dependent patterns—content behind tabs and accordions can disappear from crawlers for the very same reason.

The fix: put schema into the raw HTML

The fix is one rule: the application/ld+json block must exist in the HTTP response before any JavaScript runs. In practice that means rendering it server-side or at build time, then proving it survived.

  • Server-side render (SSR) the schema. Emit the JSON-LD in your server component or template so it ships in the initial bytes. Next.js, Nuxt, Astro, and SvelteKit all support this in their SSR or static modes.
  • Static-generate (SSG) where content is stable. Prebuild the page—schema included—so every request serves complete HTML.
  • Prerender legacy SPAs. If you can't move off client rendering, prerender the routes that need structured data so bots get a fully-formed snapshot.
  • Retire GTM for schema. Move JSON-LD out of the tag manager and into the template. Keep GTM for analytics, not structured data.

Choosing between these is the server-side vs client-side rendering decision applied to one asset. Whichever route you take, re-run the raw-HTML curl check afterward. The only proof the fix worked is the schema showing up in the bytes, not in the browser. Retrievable evidence is what earns AI citations; invisible markup earns none.

Server-side rendered page source showing an application/ld+json script present in the raw HTML bytes

A pre-publish verification checklist

Before you ship a page that depends on structured data, confirm each gate. This routine prevents the most common regression in generative engine optimization work.

  1. curl the URL and confirm the JSON-LD is in the raw response (Gate 1).
  2. Fetch with GPTBot and a generic UA—confirm identical output (no cloaking).
  3. Open the Rich Results Test in URL mode—confirm it renders and detects the type (Gate 2).
  4. Validate the JSON in the Schema.org validator (Gate 3).
  5. Check for duplicates: exactly one primary entity per page unless intentionally nested.
  6. After any deploy, re-run step 1—hydration and build changes silently break retrieval.

Bake this into release QA and into ongoing AI search monitoring so a framework upgrade never quietly deletes your schema. A tool that tracks how assistants describe your brand will flag the downstream symptom—your entity going fuzzy in ChatGPT or Perplexity—but the raw-HTML check is what tells you why.

Frequently asked questions

Do AI crawlers read JSON-LD at all?

Yes—when it is in the raw HTML. GPTBot, ClaudeBot, and PerplexityBot parse the HTML your server returns, including inline application/ld+json blocks. What they can't do is execute JavaScript, so any schema added after load is invisible to them. Placement, not format, is the deciding factor.

Is schema injected by Google Tag Manager visible to ChatGPT?

No. GTM fires client-side JavaScript after the page loads, and ChatGPT's crawlers don't render JavaScript. The tag exists only in the rendered DOM, which those bots never build. Move the JSON-LD into your server-rendered HTML if you want it retrieved.

Will Google still see my JavaScript-injected schema?

Usually yes, but on a delay. Googlebot renders JavaScript on a deferred pass, so client-injected schema can reach Google Search and AI Overviews eventually. That doesn't help ChatGPT, Claude, or Perplexity, which rely on their own non-rendering crawlers. Rendering by one engine is not visibility across all of them.

Does server-side rendering guarantee my schema is retrieved?

SSR gets the block into the raw bytes, which clears the retrieval gate for every crawler. It does not guarantee the markup is valid or free of duplicates. Always finish with a validator check and a post-deploy curl to confirm nothing stripped it during hydration.

Which schema types does this affect?

All of them. The retrieval problem is about where the markup lives, not what it declares—so Organization, Product, FAQPage, BreadcrumbList, and Article are equally at risk. If any of them is injected client-side, non-rendering crawlers miss it.

Can I just serve my schema as a separate static .json file?

Not for discovery. Search and AI crawlers look for JSON-LD inline in the page's HTML, not by guessing at a standalone file. Embed the <script type="application/ld+json"> in the server-rendered document so it travels with the page a crawler already requests.


Written by

Founder of MaxAEO. Helping brands get found in AI search across ChatGPT, Perplexity, Google AI Overviews, and more.

Run a free AI visibility audit →