aiwebpageseo JSON-LD Checker Fixes Fix JSON-LD in Next.js (App Router, RSC)

How to Fix JSON-LD in Next.js (App Router, RSC)

Next.js Metadata API handles most SEO meta but not JSON-LD — schema must be added manually via <script> tags. With React Server Components this is clean; with hydration and dynamic data it requires care. This guide covers Next.js JSON-LD. Pair with JSON-LD guide.

Step-by-step: How to fix Next.js JSON-LD

  1. Render JSON-LD from Server Components. In page.tsx or layout.tsx: const jsonLd = {...}; return (<><script type='application/ld+json' dangerouslySetInnerHTML={{__html: JSON.stringify(jsonLd)}} />...</>). Renders server-side, ships in initial HTML — perfect for crawlers.
  2. Build dynamic schema from data. Fetch data in Server Component → build JSON-LD object using fetched data → render. For an Article page: const article = await getArticle(slug); const ld = { '@context': 'https://schema.org', '@type': 'Article', headline: article.title, ... }.
  3. Centralise schema builders. src/lib/schema.ts: functions like buildArticleSchema(article), buildOrganizationSchema(), buildBreadcrumbSchema(crumbs). Reuse across pages. Type-safe with TypeScript.
  4. Add to layout for site-wide schema. Organization, WebSite belong in root layout.tsx — appears on every page. Article, Product, FAQPage belong on respective pages.
  5. Validate. Rich Results Test on representative URLs. Search Console Enhancements after deploy. Both should be clean.
  6. Fix common Next.js JSON-LD errors. Common: not stringifying object (passing object directly to dangerouslySetInnerHTML — needs JSON.stringify). Missing @context. Wrong @type. Date fields not in ISO 8601. Audit each via Rich Results Test errors.
  7. Monitor. Search Console weekly. Schema updates affect rich result eligibility — catch regressions fast.
Tip. Pin your framework, dependency, and config versions in a single internal doc (Next.js version, React version, rendering strategy choices, custom config). When something breaks after a framework or library update, you have a baseline to compare against.

The one rule that makes all of this work

Everything in this guide reduces to a single principle: put the JSON-LD in the response your server sends.

The App Router makes that the easy path, which is the good news — a script tag rendered from a Server Component ships in the initial HTML, and the problem simply does not arise. The trouble starts when somebody reaches for a useEffect, a client component, or a third-party tag manager to inject schema after load.

It is worth being precise about why that is a bad bet, because the usual explanation is too narrow:

💡 The ten-second check: curl the URL, or view source — the raw response, not the DOM in devtools, which shows the page after scripts have run — and search for ld+json. If it is not there, it is not in the served HTML, and every consumer that does not render is blind to it.

Three gates, and everyone stops after one

Section 5 says to validate, and validation answers less than teams assume. There are three questions, and most projects only ever ask the first.

1. Does it parse?
   A JSON validator answers this.
   Necessary, and proves very little.

2. Does it qualify?
   Rich Results Test answers this.
   Valid JSON with a missing required property,
   or a type with no enhancement, parses perfectly
   and is used for nothing.

3. Is it TRUE?
   Nothing answers this. And it is the one that costs money.

That third gate is where the real damage is. A Product block asserting £29.99 while the page renders £34.99 is valid JSON, valid schema, and passes every automated test in existence. It is also a false statement about your own product in machine-readable form — grounds for item disapproval in Google's shopping surfaces, and a broken promise to the shopper who clicked expecting the price they were shown.

In a Next.js codebase this failure has a specific and very common shape: the schema builder and the component read from different sources, or one reads a cached value and the other does not. The page renders the live price; the JSON-LD serialises whatever the builder was handed. Both are correct in isolation, and together they publish a lie.

Build the schema from the same data the page renders

The centralised-builders advice in section 3 is right, and it is worth stating the constraint that makes it valuable rather than merely tidy.

The schema builder must receive the same object the component renders. Not a second fetch. Not a cached copy. Not a lightly different shape assembled for SEO purposes. The same object — because the moment there are two sources of truth, they will diverge, and the divergence will be silent.

// Wrong — two sources, guaranteed to drift
const article = await getArticle(slug);
const ld = buildArticleSchema(await getArticleMeta(slug));

// Right — one source, cannot drift
const article = await getArticle(slug);
const ld = buildArticleSchema(article);
return (<>
  <JsonLd data={ld} />
  <ArticleBody article={article} />
</>);

TypeScript helps here more than it usually does: a builder typed against your domain model cannot silently accept a stale or partial object, and a schema type that requires author will fail the build rather than fail quietly in production three weeks later.

Not every rich result still exists

Worth knowing before you spend a sprint on markup that cannot pay for itself. Google has withdrawn enhancements over time, and guidance written before the changes is still circulating widely.

Still produce rich results:
  Product, Recipe, Review snippets, Breadcrumb,
  Event, Video, and others

No longer produce rich results:
  HowTo — removed entirely
  FAQ  — restricted to recognised government
         and health sites (2023). A commercial page
         gets no dropdowns, however clean the markup.

This does not make FAQPage or HowTo markup pointless. It makes the structure of a page explicit in the served HTML, so a parser does not have to infer which passage answers which question — a real benefit, and a modest one. What it does mean is that if a ticket says "add FAQ schema to get rich results", the ticket is wrong about the outcome, and somebody should know that before the work is scheduled.

And be honest about the AI angle too, since it is now the usual justification: structured data disambiguates what your text means. There is no verified evidence that denser schema wins citations from assistants, and nobody outside those companies publishes their retrieval logic.

Where Next.js projects actually get this wrong

The failures are consistent across codebases, and none of them is exotic. Each is the kind of thing that ships on a Tuesday and is noticed the following quarter.

1. Schema in a client component
   'use client' at the top of the file, a useEffect
   injecting the script. Works in devtools. Absent
   from the served HTML.

2. Two sources of truth
   The page renders live data; the schema builder
   receives a cached or separately-fetched copy.
   Both are correct. Together they publish a lie.

3. Schema on the layout that should be on the page
   Organization and WebSite belong in the root layout.
   Article, Product and FAQPage belong on the page —
   put them in a layout and every page in the segment
   claims to be the same article.

4. Optional properties assumed required
   A builder that omits 'author' when the field is
   null produces markup that validates as JSON and
   fails the Rich Results Test silently.

5. Dates serialised wrong
   datePublished and dateModified must be ISO 8601.
   A Date object stringified by a template is not.

Every one of these renders a perfectly good-looking page. That is the defining property of this whole category: nothing looks broken, and nothing tells you.

A note on libraries

The FAQ below mentions next-seo and rolling your own helpers, and the trade-off is worth stating plainly, because it is not really about convenience.

A library gives you components for common types and a shape to fill in. What it cannot give you is the thing that matters — a guarantee that the object you hand it is the same object your page renders. That guarantee comes from how you wire the data, not from the library, and no dependency will supply it.

So the choice is less consequential than it looks. What matters, either way:

Get those four right and the library is an implementation detail. Get them wrong and no library will save you — it will simply generate immaculate, well-formed markup that describes a page you are not serving.

Assert it in CI, because nothing else will tell you

Broken structured data is unique among bugs: it produces no error, no failing build, no user complaint. The page renders perfectly. The enhancement quietly stops appearing as pages are recrawled — over days or weeks, so there is not even a clean date to correlate against the deploy that caused it.

Which means a manual validation step will be skipped on exactly the release that needed it. The check has to be automatic.

In CI, against the built output (not the source):
  For one representative URL per template —
  product, article, category, homepage:

  1. Fetch the rendered HTML
  2. Assert the JSON-LD parses
  3. Assert required properties are present
  4. Assert the values match what the page renders
     (price === rendered price, headline === h1)

One URL per template is enough, because a template regression breaks every page it renders at once. Watching a hundred product pages while watching no article page is coverage theatre — it leaves an entire template unobserved, which is exactly how an author-block regression gets in unnoticed.

📐 Validate Next.js JSON-LD

Find missing fields, syntax errors and validation issues.

Run JSON-LD Validator →

Frequently Asked Questions

Can I add JSON-LD via Next.js Metadata API?

No — Metadata API handles <meta> tags, <title>, <link rel='canonical'>, OG, Twitter. JSON-LD is a <script> tag, not a meta tag. Render JSON-LD manually as <script type='application/ld+json'> in your page component or layout.

Should JSON-LD be in layout.tsx or page.tsx?

Both. layout.tsx: site-wide schema (Organization, WebSite, SiteNavigationElement) — renders on every page. page.tsx: page-specific schema (Article on blog post, Product on product page, FAQPage on FAQ). Layered approach is clean.

How do I add Article schema to Next.js blog posts?

app/blog/[slug]/page.tsx: fetch article data, build Article JSON-LD with headline, image, datePublished, dateModified, author (Person), publisher (Organization), mainEntityOfPage. Render via dangerouslySetInnerHTML. Type-safe with a buildArticleSchema helper.

Why does Search Console show 'missing field' on Next.js JSON-LD?

Required fields missing from your generated object. Common: Article missing author or publisher; Product missing offers; FAQPage missing mainEntity. Rich Results Test shows exactly which fields. Fix in schema builder helper.

Best Next.js libraries for JSON-LD?

next-seo (popular, includes JSON-LD components for common types). Or roll your own helpers (more control, no library dependency). For typed schema, schema-dts (TypeScript types for schema.org) prevents shape errors at compile time.

Does JSON-LD injected on the client work?

It is a bad bet, and the reason is broader than Google. Google does render, so schema added by a client-side effect can be picked up — but rendering is queued and happens after the initial crawl, which makes it slower and less certain than markup that was simply in the response. More importantly, many AI retrieval fetchers parse the served HTML and never execute JavaScript at all, so client-injected schema is invisible to them permanently rather than merely late. In an App Router project this is free to get right: render the script from a Server Component and it ships in the initial HTML.

Why does my schema pass validation and still not show a rich result?

Because validation and eligibility are different gates, and there is a third one nobody checks. Valid JSON means it parses. Valid schema means the required properties are present. Neither means Google will show an enhancement — some types no longer have one at all: FAQ rich results were restricted to recognised government and health sites in 2023, and HowTo rich results were removed entirely. And neither validator checks whether the markup is true: a Product block asserting a price the page does not display parses cleanly, passes every test, and is grounds for item disapproval in Google's shopping surfaces.

Got a problem?

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical website tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.