昨年メディアのような SEO が大切になる Next.js の案件があり、ページの構造を表すパンくずリスト (BreadcrumbList
) も重要なのでちゃんと構造化されたものを作ってみたのでそのメモ。
構成
- Next.js v13 (Pages Router)
- React v18
- TypeScript v5.2
パンくずリスト( BreadcrumbList
) の構造化データ
Google が公開しているページを参考にした
古の昔はパンくずリストの HTML にプロパティを付ける RDFa や microdata のような形式が主流だった記憶だが、案件を実装当時 (2023年末) では構造を <script type="application/ld+json">
として別途マークアップする JSON-LD が推奨されているらしい
構造化データに関する一般的なガイドライン
リッチリザルトを表示できるようにするには、サポートされている 3 つの形式のいずれかを使用して、サイトのページをマークアップする必要があります。
JSON-LD(推奨)
cf. Google 検索上の構造化データガイドライン | Google 検索セントラル | ドキュメント | Google for Developers
パンくずリストの JSON-LD
{ "@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" }] }
cf. パンくずリスト(BreadcrumbList)のマークアップを追加する方法 | Google 検索セントラル | ドキュメント | Google for Developers
"@context": "https://schema.org"
, "@type": "BreadcrumbList"
の部分は固定なので itemListElement
内のリストを動的に作成すればOK、更に現在のページの URL (item
) は不要なのでサイトトップと現在のページの親のデータを持たせておけば良さそうです
JSON-LD なパンくずリストの実装
// types/breadcrumb.d.ts export type BreadcrumbItem = { '@type': 'ListItem'; position: number; name: string; // title item?: string; // URL }; export interface BreadcrumbJsonLd { '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement: BreadcrumbItem[], }
パンくずリストの HTML と JSON-LD を出力するコンポーネント
// /components/BreadcrumbList.tsx import { FC } from 'react'; import Link from 'next/link'; import Script from 'next/script'; import { BreadcrumbJsonLd, BreadcrumbItem } from '@/types/breadcrumb'; type BreadcrumbListProps = { breadcrumbData: BreadcrumbItem[]; }; export const BreadcrumbList: FC<BreadcrumbListProps> = ({ breadcrumbData }) => { return ( <> <JsonLd breadcrumbData={breadcrumbData} /> <ol className='breadcrumb'> {breadcrumbData.map((item) => ( <li key={item.position} className="breadcrumbItem"> <BreadcrumbItem {…item} /> </li> )} </ol> </> ); }; const BreadcrumbItem: FC<BreadcrumbItem> = ({ name, item }) => { const label = name === SITE_NAME ? 'Home' : name; if (item) { return <Link href={item} className='breadcrumbLink'>{title}</Link>; } // current return <span className="breadcrumbLink current">{label}<span>; } // JSON-LD を出力するコンポーネント type JsonLdProps = { breadcrumbData: BreadcrumbItem[]; }; const JsonLd: FC<JsonLdProps> = ({ breadcrumbData }) => { const jsonLdSchema: BreadcrumbJsonLd = { '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement: breadcrumbData, }; return ( <Script id='BreadcrumbList-JSON-LD' type='application/ld+json' strategy='beforeInteractive' > {JSON.stringify(jsonLDSchema)} </Script> ); };
パンくずリスのデータを各ページから渡して出力すればOK
import { BreadcrumbItem } from '@/types/breadcrumb'; import { BreadcrumbList } from '@/components/BreadcrumbList'; const breadcrumbData: BreadcrumbItem[] = [ { '@type': 'ListItem', position: 1, name: SITE_NAME, item: SITE_HOME_URL, }, { '@type': 'ListItem', position: 2, name: PARENT_PAGE_NAME, item: PARENT_PAGE_URL, }, { '@type': 'ListItem', position: 3, name: THIS_PAGE_TITLE } ] ; export default function Page() { return ( <PageLayout> <BreadcrumbList breadcrumbData={breadcrumbData} /> <PageContent> </PageLayout> ); }
動的にページが決まる場合は breadcrumbData
を動的に作成してしまえば OK。
こんな感じで構造化された JSON-DL のパンくずリストを作成することができました!
今回は基本的に SSG をする仕様だったので JSON-DL を出力する Script
に strategy='beforeInteractive'
を指定してページが表示される前に JSON-LD のスクリプトがロードされるようにしたが、JSON-LD と next/script の strategy
の仕様に明るくないので strategy
の最適な指定方法が理解できているわけではないです。
おわり
[参考]
- パンくずリスト(BreadcrumbList)のマークアップを追加する方法 | Google 検索セントラル | ドキュメント | Google for Developers
- json-ldの仕組みとは?SEO効果と構造化マークアップの方法を解説 | WEB集客ラボ byGMO(GMO TECH)
- Components: <Script> | Next.js
app router も Remix もまだ全然さわれてないのでそろそろ危機感覚えてきてる…