seo: move site-wide JSON-LD from body to head - #608
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope
Move the site-wide Organization + WebSite JSON-LD block from the first child of
<body>into<head>of the root layout.Rationale
Google parses JSON-LD in both
<head>and<body>, but stricter parsers (some AI search crawlers, schema validators) only scan<head>for structured data. The Organization block carries the brand entity, logo, contactPoint, sameAs (including the Wikidata anchor) and SearchAction. Placing it in<head>makes it visible to head-only parsers on every page of the site.Pure placement change. No schema content delta. The serialized JSON is byte-identical to the previous output.
Per-page JSON-LD (intentionally out of scope)
The original brief also called for moving per-page Dataset/TechArticle/Breadcrumb/FAQPage scripts on
/benchmarks/[slug],/products/[slug],/answers/[slug],/alternatives/[slug],/compare/[slug],/chains/[slug], and the various hub pages. After verifying againstnode_modules/next/dist/docs/01-app/02-guides/json-ld.mdandreact-dom's source (pushScriptImplat server.node.production.js:1839), this is not achievable cleanly in Next 16 + React 19 App Router:<script>rendered from a page component lands in<body>because the page renders inside the root layout's<body>(insertion mode HTML_BODY_MODE). React 19 only hoists<script>when it hassrc + async(resource script), not for inline JSON-LD.next/scriptwithstrategy=beforeInteractiveand inline content serializes into aself.__next_s.push(...)runtime call in<body>, not a literal<script type="application/ld+json">in head. The Next docs explicitly recommend native<script>overnext/scriptfor JSON-LD.<head>element rendered from a child page produces a duplicate<head>tag inside<body>(React generic element path, not preamble path).otherfield only emits<meta>tags, not<script>.React.cache-backed bus is not shared betweengenerateMetadataresolution, layout render and page render (verified empirically: layout readslen=0before the page push runs).Per-page JSON-LD therefore stays where it is. Google rich results coverage is unaffected (Google parses body fine).
Verification
Local
pnpm buildthen count of literal<script type="application/ld+json">opening tags in head vs body per prerendered HTML:The site-wide
head=1confirms the Organization + WebSite block now ships in<head>on every prerendered page.body=Ncounts are the per-page Dataset/Article/Breadcrumb/FAQPage blocks, unchanged.Checks
pnpm typecheckpnpm lint --fix(only pre-existing warnings)pnpm build