【Next.js】構造化データ (JSON-LD) を型安全に実装する (パンくずリストを添えて)
サイト内で記事(Article、NewsArticle、BlogPosting) やパンくずリスト(BreadcrumbList)などの構造化データ (JSON-LD) を実装したい場面が多々ある。
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [{ "@type": "ListItem", "position": 1, "name": "Books", "item": "https://example.com/books" },{ "@type": "ListItem", "position": 2, "name": "Science Fiction", "item": "https://example.com/books/sciencefiction" },{ "@type": "ListItem", "position": 3, "name": "Award Winners" }] }</script>
しかしNext.jsで提供されているMetadata APIに該当の機能は含まれておらず、自前で上記のようなオブジェクトを定義して<script>
内に埋め込む必要がある。
そこで調べたところGoogle製のschema-dtsを使うことでTypeScriptの型安全性を享受できそうだったので、今回はパンくずリストを例に構造化データを実装してみる。
1. schema-dtsをインストール
npm i -D schema-dts
2. BreadcrumbListのオブジェクトを生成
import type { BreadcrumbList, WithContext } from 'schema-dts';
export type BreadcrumbItem = { pathname: string; title: string;};
export function createBreadcrumbJsonLd( items: BreadcrumbItem[],): WithContext<BreadcrumbList> { return { '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement: items.map(({ pathname, title }, i) => ({ '@type': 'ListItem', position: i + 1, name: title, item: pathname, })), };}
schema-dts
から提供される型 (今回はBreadcrumbList
) を利用することで、上記のように型安全に対象スキーマのオブジェクトを生成できる。もちろんitemListElement
の各要素にも型補完が効くのでとても便利。
もし@context
を含めたい場合はWithContext
でラップする。
3. <script>内に埋め込む
import type { ReactElement } from 'react';import type { Thing, WithContext } from 'schema-dts';
type Props = { schema: WithContext<Thing>;};
export default function JsonLd({ schema }: Props): ReactElement { return ( <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }} /> );}
JSON-LD出力用のコンポーネントを実装する。Propsの型をThing
にすることでschema-dts
のスキーマの型を幅広く受け入れるようにしている。
4. パンくずリストのコンポーネントを実装
import type { ReactElement } from 'react';import Link from 'next/link';import JsonLd from '@/presentation/components/common/jsonLd';import { type BreadcrumbItem, createBreadcrumbJsonLd,} from '@/domain/utils/schema/breadcrumb';
type Props = { items: BreadcrumbItem[];};
export default function Breadcrumb({ items }: Props): ReactElement { return ( <> <ol> {items.map(({ pathname, title }, i) => ( <li key={pathname}> {items.length === i + 1 ? ( title ) : ( <Link href={pathname}>{title}</Link> )} </li> ))} </ol> <JsonLd schema={createBreadcrumbJsonLd(items)} /> </> );}
先ほど実装した汎用関数でJSON-LDを生成し、パンくずリスト本体と共に出力するコンポーネントを実装する (JSON-LDは<body>
内に含めても無問題) 。
export default async function Page(): Promise<ReactElement> { return ( <Breadcrumb items={[ { pathname: 'https://example.com', title: 'トップ', }, { pathname: 'https://example.com/categories', title: 'カテゴリ一覧', }, { pathname: 'https://example.com/posts/xxx', title: 'xxx', }, ]} /> );}
あとは上記のようにサイト構造やページ階層に応じて適切なitems
を渡してあげる。
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": [ { "@type": "ListItem", "position": 1, "name": "トップ", "item": "https://example.com" }, { "@type": "ListItem", "position": 2, "name": "カテゴリ一覧", "item": "https://example.com/categories" }, { "@type": "ListItem", "position": 3, "name": "xxx", "item": "https://example.com/posts/xxx" } ] }</script>
これで目当ての構造化データが出力された。