Skip to content

seo: move site-wide JSON-LD from body to head - #608

Merged
Flotapponnier merged 1 commit into
devfrom
seo/jsonld-head
Jun 21, 2026
Merged

seo: move site-wide JSON-LD from body to head#608
Flotapponnier merged 1 commit into
devfrom
seo/jsonld-head

Conversation

@Flotapponnier

Copy link
Copy Markdown
Collaborator

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 against node_modules/next/dist/docs/01-app/02-guides/json-ld.md and react-dom's source (pushScriptImpl at server.node.production.js:1839), this is not achievable cleanly in Next 16 + React 19 App Router:

  • Inline <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 has src + async (resource script), not for inline JSON-LD.
  • next/script with strategy=beforeInteractive and inline content serializes into a self.__next_s.push(...) runtime call in <body>, not a literal <script type="application/ld+json"> in head. The Next docs explicitly recommend native <script> over next/script for JSON-LD.
  • A <head> element rendered from a child page produces a duplicate <head> tag inside <body> (React generic element path, not preamble path).
  • The metadata other field only emits <meta> tags, not <script>.
  • A React.cache-backed bus is not shared between generateMetadata resolution, layout render and page render (verified empirically: layout reads len=0 before 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 build then count of literal <script type="application/ld+json"> opening tags in head vs body per prerendered HTML:

index.html: head=1, body=0
benchmarks.html: head=1, body=1
about.html: head=1, body=2
contribute.html: head=1, body=2
chains.html: head=1, body=2
partners.html: head=1, body=1
press.html: head=1, body=2
methodology.html: head=1, body=3
prediction-markets.html: head=1, body=1
hyperliquid.html: head=1, body=1
answers.html: head=1, body=1
compare.html: head=1, body=1
products.html: head=1, body=1
alternatives.html: head=1, body=1

The site-wide head=1 confirms the Organization + WebSite block now ships in <head> on every prerendered page. body=N counts are the per-page Dataset/Article/Breadcrumb/FAQPage blocks, unchanged.

Checks

  • pnpm typecheck
  • pnpm lint --fix (only pre-existing warnings)
  • pnpm build

@Flotapponnier
Flotapponnier merged commit cc66e3c into dev Jun 21, 2026
1 check passed
@Flotapponnier
Flotapponnier deleted the seo/jsonld-head branch June 21, 2026 12:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant