GioJSdocs
On this page

<JsonLd>

Render schema.org structured data as a JSON-LD script block, escaped so that no value can break out of it.

tsx
import { JsonLd } from '@gio.js/react';

<JsonLd data={{ '@context': 'https://schema.org', '@type': 'Organization', name: 'Acme', url: 'https://acme.example' }} />

Reference

PropTypeDefaultDescription
data (required)JsonLdData-One schema.org object, or an array of them: Record<string, unknown> | ReadonlyArray<Record<string, unknown>>.
idstring-An id for the <script> element, for example to find it in tests.

The props and data types are exported as JsonLdProps and JsonLdData.

Returns

A <script type="application/ld+json"> element holding JSON.stringify(data), rendered where you place the component. Inside it, <, > and & are written as \u003c, \u003e and \u0026, and the line separators U+2028 and U+2029 as \u2028 and \u2029. Every JSON parser reads the same data, but a string such as a post title containing </script> can no longer end the element and inject markup.

text
<script type="application/ld+json">{"@context":"https://schema.org","@type":"Article","headline":"Hello \u003c/script\u003e\u003cb\u003ex\u003c/b\u003e \u0026 more"}</script>

When JSON.stringify gives nothing (data is undefined at run time), nothing is rendered.

Examples

An article

app/blog/[slug]/page.tsx
import type { GetServerSideProps } from '@gio.js/core';
import { JsonLd } from '@gio.js/react';

interface Post {
  slug: string;
  title: string;
  author: string;
  publishedAt: string;
}

export const getServerSideProps: GetServerSideProps<{ post: Post }, '/blog/:slug'> = async (ctx) => {
  return { props: { post: await posts.get(ctx.params.slug) } };
};

export default function PostPage({ post }: { post: Post }) {
  return (
    <article>
      <JsonLd
        data={{
          '@context': 'https://schema.org',
          '@type': 'BlogPosting',
          headline: post.title,
          author: { '@type': 'Person', name: post.author },
          datePublished: post.publishedAt,
          url: `https://acme.example/blog/${post.slug}`,
        }}
      />
      <h1>{post.title}</h1>
    </article>
  );
}

Several items in one block

Pass an array, or one object with an @graph:

tsx
<JsonLd
  data={{
    '@context': 'https://schema.org',
    '@graph': [
      { '@type': 'WebSite', name: 'Acme', url: 'https://acme.example' },
      {
        '@type': 'BreadcrumbList',
        itemListElement: [
          { '@type': 'ListItem', position: 1, name: 'Blog', item: 'https://acme.example/blog' },
          { '@type': 'ListItem', position: 2, name: 'Launch notes' },
        ],
      },
    ],
  }}
/>

Site-wide data in the root layout

The block is plain HTML, so it works in the server-only root layout too, where it appears on every page.

app/layout.tsx
import type { LayoutProps } from '@gio.js/core';
import { JsonLd } from '@gio.js/react';

export default function RootLayout({ children }: LayoutProps) {
  return (
    <html lang="en">
      <body>
        <JsonLd data={{ '@context': 'https://schema.org', '@type': 'Organization', name: 'Acme', logo: 'https://acme.example/logo.png' }} />
        {children}
      </body>
    </html>
  );
}

Good to know

  • The type application/ld+json makes it a data block the browser never runs, so it needs no CSP nonce, also under a strict Content Security Policy.
  • The escaping is fixed: it is what makes rendering user content into the block safe.
  • JSON.stringify rules apply: Dates become ISO strings, undefined values and functions are dropped, and a BigInt or a circular object throws during the render.
  • GioJS does not check the vocabulary. Validate the output with a structured-data testing tool.

Version history

VersionChanges
v0.1.0-beta.8Introduced.