コンテンツへスキップ

JSON-LD構造化データの基礎 — 検索結果のリッチリザルトはどう生成されるか

検索結果に、記事の投稿日や著者名、パンくずリスト(サイト内の階層)が添えて表示されているのを見たことがあるはずだ。こうした「通常の青いリンク+説明文」より情報が多い表示は、リッチリザルトと呼ばれる。その材料になっているのが、ページに埋め込まれた構造化データである。今回は、構造化データの中でも現在主流のJSON-LDについて、何を書くもので、どう検証するのかを、このブログ(wpmm.jp/blog と en.wpmm.jp/blog)の実装を例に整理する。

補足: 構造化データは、ページの内容を「機械が誤解なく読める形」で添える注釈である。書いたからといって特定の表示が保証されるものではなく、あくまで検索エンジンが内容を理解するための手がかりという位置づけになる。

構造化データが解決しようとしていること

人間はページを見れば「これは記事で、この日付に公開され、この人が書いた」と分かる。しかし検索エンジンは、HTMLの見た目から意味を推測しなければならない。日付らしき文字列が投稿日なのか更新日なのか、名前らしき文字列が著者なのか引用元なのかは、文脈から推測するしかない。

構造化データは、この推測の余地を減らす。「この部分は投稿日」「この部分は著者」と、意味のラベルを付けた形で別に書いておく。ラベルの語彙は schema.org という共通の辞書で決められており、BlogPosting(ブログ記事)、Organization(組織)、BreadcrumbList(パンくずリスト)といった型が用意されている。

書き方は3種類あるが、JSON-LDが扱いやすい

構造化データをページに載せる方式には、主に次の3つがある。

方式 書く場所 特徴
Microdata 既存のHTMLタグの属性 表示用のHTMLと混ざるため、テンプレートの変更が絡みやすい
RDFa 既存のHTMLタグの属性 同上
JSON-LD 独立した <script> ブロック 表示用のHTMLと分離でき、データだけをまとめて出力できる

JSON-LDは、<head> などに次のような script タグとして置く。

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "記事のタイトル",
  "datePublished": "2026-09-21T08:00:00+09:00",
  "author": { "@type": "Person", "name": "著者名" }
}
</script>

見た目のHTMLには一切触れず、データだけを別ブロックとして出力できる点が、テーマやテンプレートを保守する側にとって大きい。表示の改修とデータの管理を分けて考えられる。

最低限知っておきたい記号

JSON-LDには、@ で始まる特別なキーがいくつかある。

  • @context: どの語彙を使うかの宣言。ほぼ常に https://schema.org を指定する
  • @type: そのデータが何の種類かの宣言(BlogPosting や Organization など)
  • @id: そのデータに付ける識別子(URLの形で書くことが多い)。他の場所から参照するときに使う

とくに @id は、複数のデータをつなぐときに役立つ。たとえば記事の発行元を、記事データの中に毎回すべて書くのではなく、@id だけで指して済ませられる。

このブログでの実例: 1つの @graph にまとめる

このブログのテーマ wpmm-blog は、各ページの <head> にJSON-LDを1ブロック出力している。中身は @graph という配列で、複数のデータを1つにまとめる形式になっている。記事ページの場合、次の種類が並ぶ。

型 役割
Organization 運営元の組織情報(名前・URL・ロゴ)
WebSite このブログサイト自体の情報(言語なども含む)
BreadcrumbList パンくずリスト(ホーム → カテゴリ → 記事、のような階層)
BlogPosting 記事そのものの情報(見出し・投稿日・更新日・著者・カテゴリなど)

ここで @id の使い方が生きてくる。組織情報は Organization として1回だけ書き、WebSite や BlogPosting の publisher からは @id で参照している。運営元の情報が1か所にしかないので、社名などを直すときに書き換える箇所が増えない。

// 説明用に簡略化した疑似コード
$graph[] = [ '@type' => 'Organization', '@id' => $base . '#organization', /* 名前・URL・ロゴ */ ];
$graph[] = [ '@type' => 'BlogPosting',  '@id' => $permalink . '#blogposting',
             'publisher' => [ '@id' => $base . '#organization' ], /* 見出し・日付など */ ];

echo '<script type="application/ld+json">';
echo wp_json_encode( [ '@context' => 'https://schema.org', '@graph' => $graph ],
                     JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
echo '</script>';

もう1点、出力にはPHPの wp_json_encode() を使っている。JSONとして正しくエスケープした文字列を生成する関数で、記事タイトルに引用符や特殊文字が含まれていても、JSONが壊れない。前々回の記事で扱った「出力時に逃がす」考え方は、JSONを出力する場面にも当てはまる。手でJSON文字列を連結して組み立てると、引用符ひとつで全体が不正になりうる。

また、投稿日と更新日は get_the_date('c') のように、ISO 8601形式(タイムゾーン付き)で出している。日付を人間向けの表記のまま入れず、機械が解釈できる形式で渡すことが、構造化データでは大事になる。

ページの見た目と食い違わせない

構造化データで守るべき原則は、ページに実際に表示している内容と一致させることである。見出しや日付を、画面に出ている内容と違う値で書くと、検索エンジンから見て不整合になり、構造化データ全体の信頼性を下げるおそれがある。

このブログでは、BlogPosting の見出し・投稿日・更新日・著者名を、いずれも記事ページ自身が持つ情報から生成している。表示側と構造化データ側で別々に値を持たず、元のデータを共有しておくと、食い違いが起きにくい。

出力後に確認するには

JSON-LDも、hreflangと同様に見た目には現れない。書いたつもりで抜けていたり、構文が壊れていたりしても気づきにくい。確認方法はいくつかある。

  • ブラウザでページのソースを開き、application/ld+json を検索して、ブロックが出力されているか見る
  • 取り出したJSONを、JSONパーサー(たとえばコマンドラインの python3 -m json.tool)に通し、構文が正しいか確認する
  • 検索エンジン各社が提供している構造化データの検証ツールで、型と必須項目が満たされているかを見る

このブログでは、記事の公開後にSEO確認用のスクリプト audit.py でページのHTMLからJSON-LDのブロックを取り出し、JSONとして解析できるかを確認している。解析に失敗すれば「PARSE ERROR」、ブロック自体がなければ「MISSING」と表示され、正常なら含まれている型(Organization / WebSite / BreadcrumbList / BlogPosting)と、見出し・日付・著者などの値が一覧で出る。公開のたびに人の目で全項目を読むより、構文エラーと欠落を機械的に拾うほうが見落としが少ない。

よくある落とし穴

  • JSONの構文ミス: 末尾のカンマや引用符の閉じ忘れで、ブロック全体が読めなくなる。文字列連結ではなく、配列からJSON化する関数で出力する
  • 表示と違う内容を書く: 画面にない情報や、実際と異なる日付を入れる
  • 日付の書式が不統一: 人間向けの表記をそのまま入れる。ISO 8601形式に揃える
  • 同じ情報の重複定義: 組織情報などを複数か所に書き、片方だけ直し忘れる。@id で1か所に集約する
  • 書けば必ず表示が変わると期待する: 構造化データは手がかりであり、リッチリザルトの表示可否は検索エンジン側が判断する

まとめ

JSON-LDは、ページの内容に意味のラベルを付けて、検索エンジンに機械可読な形で渡すための仕組みである。表示用のHTMLと分離した <script> ブロックで書けるため、テーマ側で1か所にまとめて管理しやすい。ポイントは、schema.orgの型を正しく選ぶこと、@id で情報を1か所に集約すること、ページの表示内容と一致させること、そして公開後に構文と欠落を確認することにある。

次回以降は、同じく <head> に置かれるOGP(SNSシェア時のカード表示)についても取り上げる予定である。