# JSON-LD

Renders a safely serialized application/ld+json script tag.

## Installation

```bash
npx shadcn@latest add https://ui.uptoolkit.com/r/json-ld.json
```

[Registry JSON](https://ui.uptoolkit.com/r/json-ld.json)

## Preview

```tsx
import { JsonLd, serializeJsonLd } from "@/components/json-ld";

const data = {
  "@context": "https://schema.org",
  "@type": "SocialMediaPosting",
  "@id": "https://social.example/users/ada/posts/1",
  text: "Structured data ships with the markup </script>",
  author: { "@type": "Person", name: "Ada Okoye" },
};

export function Preview() {
  return (
    <div className="flex w-full max-w-xl flex-col gap-2 text-left">
      <JsonLd data={data} id="preview-json-ld" />
      <p className="m-0 text-sm text-muted-foreground">
        The script tag is in the document. Its escaped contents:
      </p>
      <pre className="m-0 overflow-auto rounded-md bg-muted p-3 text-xs">
        {serializeJsonLd(data, 2)}
      </pre>
    </div>
  );
}
```


## Source

### components/json-ld.tsx

```tsx
/**
 * Renders JSON-LD structured data as an `application/ld+json` script tag.
 *
 * @see https://json-ld.org/spec/latest/json-ld/#embedding-json-ld-in-html-documents
 */

/** Kept as named constants so the source file stays pure ASCII. */
const LINE_SEPARATOR = "\u2028";
const PARAGRAPH_SEPARATOR = "\u2029";

const scriptEscapes: Record<string, string> = {
  "<": "\\u003c",
  ">": "\\u003e",
  "&": "\\u0026",
  [LINE_SEPARATOR]: "\\u2028",
  [PARAGRAPH_SEPARATOR]: "\\u2029",
};

const unsafeScriptCharacters = /[<>&\u2028\u2029]/gu;

function escapeJsonForScript(json: string): string {
  return json.replace(unsafeScriptCharacters, (char) => scriptEscapes[char] ?? char);
}

/**
 * Serializes JSON-LD for inline embedding.
 *
 * `<`, `>`, and `&` are escaped so a value containing `</script>` cannot close
 * the tag early, and U+2028/U+2029 are escaped because they are valid in JSON
 * but illegal raw inside a JavaScript string literal.
 */
function serializeJsonLd(data: unknown, space?: number): string {
  return escapeJsonForScript(JSON.stringify(data, null, space) ?? "null");
}

type JsonLdProps = {
  /** A single node, or several nodes rendered as a JSON-LD array. */
  data: unknown;
  /** Optional `id` so a framework can dedupe or replace the tag. */
  id?: string;
  /** Pretty-print with the given indent. Useful while debugging. */
  space?: number;
};

function JsonLd({ data, id, space }: JsonLdProps) {
  if (data === null || data === undefined) {
    return null;
  }

  if (Array.isArray(data) && data.length === 0) {
    return null;
  }

  return (
    <script
      type="application/ld+json"
      id={id}
      suppressHydrationWarning
      // Escaped by serializeJsonLd: JSON.stringify output is never raw markup.
      dangerouslySetInnerHTML={{ __html: serializeJsonLd(data, space) }}
    />
  );
}

export { JsonLd, serializeJsonLd, type JsonLdProps };
```



## Usage

Emits structured data as an `application/ld+json` script tag. Works in a server component, a client
component, or anywhere else you can render an element.

```tsx
import { JsonLd } from "@/components/json-ld";

<JsonLd data={{ "@context": "https://schema.org", "@type": "Person", name: "Ada Okoye" }} />
```

Pass an array to emit several nodes at once. `null`, `undefined`, and empty arrays render nothing, so
you can hand it optional data without guarding the call site.

### Escaping

`JSON.stringify` alone is not safe to interpolate into HTML. `serializeJsonLd` escapes `<`, `>`, and
`&` so a value containing `</script>` cannot terminate the tag early, and escapes U+2028 / U+2029,
which are legal in JSON but not inside a JavaScript string literal.

```tsx
serializeJsonLd({ text: "</script><script>alert(1)</script>" });
// {"text":"</script><script>alert(1)</script>"}
```

Pass `space` to pretty-print while debugging, and `id` if your framework needs to dedupe or replace
the tag between navigations.

