Invalid structured data (JSON-LD) in Next.js
You added JSON-LD, but it’s malformed or missing its schema.org @context, so crawlers parse it, fail, and skip it — you get zero credit for the effort. This is worse than a silent miss because it looks done. Hand-authored or string-concatenated JSON-LD (common in AI output) is exactly where trailing commas and unescaped quotes creep in.
Framework fixes
Symptoms
How VibeCheck catches it
In your widget · Problems
Structured data is invalid
To your coding agent · MCP
agent › get_detected_issues
→ { detector: "aeo", issue: "Structured data is invalid", threshold: "A JSON-LD script exists but fails JSON.parse or doesn’t reference schema.org" }
The same string in your widget and in your agent’s context — no screenshot, no copy-paste.
Root causes
The fix for Next.js
Render the JSON-LD from a Server Component with JSON.stringify. It runs server-side, so the escaped block ships in the initial HTML where crawlers read it — no client hydration needed.
export default function Page() {
const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', headline }
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
)
}Steps
- Replace hand-written JSON strings with
JSON.stringifyof a typed object - Confirm
@contextis"https://schema.org"and@typeis a valid type - Run it through Google’s Rich Results Test until it passes clean
FAQ
- How do I know what’s wrong with my
JSON-LD? - Paste it into Google’s Rich Results Test or the
schema.orgvalidator — both point to the exact line and field. Most failures are a JSON syntax error or a missing@context. - Why does string interpolation cause invalid
JSON-LD? - A title with a quote, apostrophe, or newline breaks the surrounding JSON when interpolated raw.
JSON.stringifyescapes those characters correctly, which is why building an object and stringifying it is the safe pattern. - Can I put several types in one
JSON-LDblock? - Yes — use an
@grapharray to declare multiple entities (for example anOrganizationand aWebSite) in a single script. Keep each node’s@typeand required fields valid: one malformed node invalidates the whole block.