---
title: "JSON-LD injected with `JSON.stringify` without escaping `<` (lint/dangerously-set-jsonld-unescaped)"
description: "lint/dangerously-set-jsonld-unescaped: `JSON.stringify` doesn't escape `<`, so any string containing `</script>` (a user name, a CMS title) closes the script…"
canonical: https://void-design.vercel.app/rules/lint/dangerously-set-jsonld-unescaped
lastModified: 2026-09-16
---

# JSON-LD injected with `JSON.stringify` without escaping `<`

`lint/dangerously-set-jsonld-unescaped` · severity **error** · category Lint · detected by `void lint` and `void audit`

## Why it matters

`JSON.stringify` doesn't escape `<`, so any string containing `</script>` (a user name, a CMS title) closes the script tag and injects HTML — a stored XSS vector — or breaks the structured data.

## How to fix it

Escape `<` when serialising: `JSON.stringify(data).replace(/</g, '\\u003c')`, via a shared `<JsonLd>` component.

## Example

```tsx
export function JsonLd({ data }: { data: object }) {
  return <script type="application/ld+json"
    dangerouslySetInnerHTML={{ __html: JSON.stringify(data).replace(/</g, '\\u003c') }} />
}
```

## References

- https://nextjs.org/docs/app/guides/json-ld

## More lint rules

`void lint` reports 67 rules in this category. Static source checks with no browser: Tailwind v4 silent failures, Next.js 16 API traps, React render-body bugs, accessibility markup, SEO files and motion hygiene.

- `scroll-listener-nonpassive` `wheel` / `touchstart` / `touchmove` listener without `{ passive: true }` — [lint/scroll-listener-nonpassive](https://void-design.vercel.app/rules/lint/scroll-listener-nonpassive)
- `unload-listener` `unload` event listener — [lint/unload-listener](https://void-design.vercel.app/rules/lint/unload-listener)
- `useeffect-fetch` Page fetches its data in `useEffect` — [lint/useeffect-fetch](https://void-design.vercel.app/rules/lint/useeffect-fetch)
- `div-button` Clickable `<div>`/`<span>` without button semantics — [lint/div-button](https://void-design.vercel.app/rules/lint/div-button)
- `transition-all` `transition: all` / `transition-all` — [lint/transition-all](https://void-design.vercel.app/rules/lint/transition-all)
- `animate-layout-prop` Animation of layout properties (width/height/top/left/margin/padding) — [lint/animate-layout-prop](https://void-design.vercel.app/rules/lint/animate-layout-prop)
- `outline-none-no-replacement` Focus outline removed without a visible replacement — [lint/outline-none-no-replacement](https://void-design.vercel.app/rules/lint/outline-none-no-replacement)
- `scale-zero-entry` Element enters from `scale(0)` — [lint/scale-zero-entry](https://void-design.vercel.app/rules/lint/scale-zero-entry)

Detected by `void lint` and `void audit`. Explain it in a terminal: `void rules lint/dangerously-set-jsonld-unescaped`
