# Invalid structured data (JSON-LD)

> Malformed JSON-LD is silently skipped by search and answer engines. Fix the syntax and schema.org @context so your structured data actually counts.

_Category: AI readiness · Detector `aeo` · Check `structured-data-invalid` · Severity: warning_

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.

## Symptoms

- Rich Results Test reports a parsing error or missing required field
- No rich results despite having a `JSON-LD` block on the page
- The script contains a trailing comma, unescaped quote, or wrong `@context`
- VibeCheck flags the `JSON-LD` as present but invalid

## How VibeCheck detects it

The `aeo` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `Structured data is invalid`
- **Threshold:** A JSON-LD script exists but fails JSON.parse or doesn’t reference schema.org

## Root causes

- Invalid JSON — trailing commas, unescaped quotes, or comments
- Missing or wrong `"@context"`: `"https://schema.org"`
- String-concatenated `JSON-LD` instead of `JSON.stringify` of a real object
- Interpolated values that weren’t escaped for JSON

## The fix

Build the structured data as a real JavaScript object and `JSON.stringify` it — never hand-concatenate JSON. Ensure the top-level `@context` is exactly `"https://schema.org"`, and validate the output. Stringifying guarantees valid escaping and no trailing commas.

### Steps

1. Replace hand-written JSON strings with `JSON.stringify` of a typed object
2. Confirm `@context` is `"https://schema.org"` and `@type` is a valid type
3. Run it through Google’s Rich Results Test until it passes clean

_Stringify an object — never concatenate JSON by hand_

```ts
// ✗ fragile: interpolation breaks on quotes/newlines in title
// `{ "headline": "${title}" }`
// ✓ correct: stringify escapes everything
const jsonLd = JSON.stringify({
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: title,
})
```

## Framework-specific fixes

### React

Render with `JSON.stringify` and `dangerouslySetInnerHTML` so the value is properly escaped exactly once — don’t build the JSON as a template string.

```tsx
<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
```

### 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.

```tsx
export default function Page() {
  const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', headline }
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
    />
  )
}
```

[Next.js docs](https://nextjs.org/docs/app/guides/json-ld)

### Vanilla JS

If you template `JSON-LD` server-side, serialize with your language’s JSON encoder, not string interpolation, so quotes and Unicode are escaped.

```js
res.write('<script type="application/ld+json">' + JSON.stringify(data) + '</script>')
```

## FAQ

### How do I know what’s wrong with my `JSON-LD`?

Paste it into Google’s Rich Results Test or the `schema.org` validator — 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.stringify` escapes those characters correctly, which is why building an object and stringifying it is the safe pattern.

### Can I put several types in one `JSON-LD` block?

Yes — use an `@graph` array to declare multiple entities (for example an `Organization` and a `WebSite`) in a single script. Keep each node’s `@type` and required fields valid: one malformed node invalidates the whole block.

## Related problems

- [Missing structured data (JSON-LD)](https://vibecheck.wcgw.fun/fix/missing-structured-data.md) — AI readiness
- [Missing author and date signals](https://vibecheck.wcgw.fun/fix/missing-author-metadata.md) — AI readiness
- [Missing llms.txt](https://vibecheck.wcgw.fun/fix/missing-llms-txt.md) — AI readiness

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/invalid-structured-data. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
