Does JSON-LD Schema Need to Be in <head>? We Tested Our Own Assumption.
We shipped a recommendation telling users to move JSON-LD out of <body>. Then we went looking for evidence it mattered to AI crawlers. We couldn't find any — so we removed it.
Most GEO and technical SEO checklists include some version of the same line: move your JSON-LD schema into <head>. It's repeated often enough that it reads as settled fact. We had it in our own scoring model too — until we tried to find the actual evidence behind it.
We couldn't find a single source — not Google's own documentation, not Next.js's, not schema.org's — that says location inside the document matters for parsing. So we went and checked what actually does.
What the actual documentation says
Next.js's own JSON-LD guide states plainly that a <script type="application/ld+json"> block can go in <head> or directly in the body — both are treated as valid. Google's structured data guidance doesn't distinguish by document section either. JSON-LD isn't a render-blocking resource like CSS, and it isn't subject to head-only parsing rules the way some meta tags are.
For a server-rendered page, the script tag is present in the raw HTML response regardless of where it sits. An AI crawler that doesn't execute JavaScript receives the same bytes whether the tag is in <head> or three levels deep in <body>.
Where the confusion probably comes from
The head-only advice likely gets inherited from real, unrelated problems that do involve <head> vs <body>: client-side-injected schema (added via JavaScript after hydration, which crawlers that don't execute JS never see at all — a rendering problem, not a location problem), and render-blocking resources like stylesheets, where placement genuinely changes behavior. Schema doesn't behave like either of those.
- Client-side injected schema — added via useEffect or similar after page load. Invisible to static crawlers. This is a real problem, and it looks superficially similar to a 'wrong location' problem.
- Render-blocking resources — CSS and synchronous scripts genuinely behave differently by placement. Schema is inert JSON, not executable, so this doesn't transfer.
- Server-rendered JSON-LD in body — present in the initial HTML either way. No JS execution required to read it.
What we found when we checked our own scoring
Brandioz's crawl score had a signal — schema_in_head — worth 12 of 100 points, penalizing schema found in <body> rather than <head>. We ran our own homepage through it and got flagged for exactly this. Investigating why turned up the real issue underneath: not location, but a duplicate SoftwareApplication type declared across two separate script blocks with different data in each.
That's the actual problem worth scoring: the same entity type defined twice, with no way for a crawler — or a human — to know which definition is authoritative.
We rewrote the signal. It no longer scores head-vs-body placement. It scores whether schema is present, whether it parses, and whether the same @type is declared more than once with conflicting data.
What to actually check on your own site
- Is your schema in the raw HTML at all? Run `curl https://yourdomain.com | grep -o 'application/ld+json'`. If nothing comes back, it's likely client-side injected — that's the real problem, not location.
- Is any @type declared more than once? Run `curl https://yourdomain.com | grep -o '"@type":"[A-Za-z]*"' | sort | uniq -c`. Anything with a count above 1 is worth investigating — check whether both definitions agree.
- Does it parse? Paste your rendered HTML into Google's Rich Results Test or schema.org's validator. Parse errors matter. Section placement doesn't.
Documented sources tying JSON-LD parse behavior to document section
Across Google, Next.js, and schema.org's own guidance — location-based advice appears to be inherited folklore, not a real requirement
Key takeaway
Schema location (<head> vs <body>) doesn't affect whether AI crawlers can read it — for server-rendered pages, the static HTML is identical either way. The real risk is the same @type declared in multiple separate script blocks with conflicting data. We corrected our own scoring model after finding this, and you should check your own site for duplicate types instead of chasing head placement.
See how your site scores
Free AI visibility analysis — takes 10 seconds.