【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>
これで目当ての構造化データが出力された。