【WordPress独自関数】構造化データ「パンくずリスト」を「JSON-LD」形式で実装する関数

SEO対策にもなるパンくずリストの構造化データを「JSON-LD」形式でWordPressに実装する方法を紹介します。
プラグインを使用すればインストールして管理画面から設定するだけで設置することも出来ますが、今回はプラグインを使わない方法を紹介したいと思います。

## 「構造化データ」とは？

「構造化データ」とは、WEBページの内容を検索エンジンにも理解できるようにHTML内に書くmeta情報です。
人間であればサイト内のコンテンツ内容を見て理解できても、クローラー（ボット）ではその認識が難しい場合があります。そのような場合、「構造化データ」はクローラーがコンテンツ内容を認識する手助けになります。
その結果、クローラーがより正しくコンテンツを認識するようになったり、検索結果にサイトが表示される際にリッチリザルトが表示されるようになったりします。
他サイトとの差別化を図っていくことなどもできるためSEOへの効果も期待できます。

> **「リッチリザルト」とは？**
> 「構造化データ」をもとに視覚的な機能や操作機能が追加された検索結果の表示のことです。

## 「JSON-LD」とは？

「JSON-LD」形式についてですが「構造化データ」を作成する際には「ボキャブラリー」と「シンタックス」が必要になります。

|項目|概要|代表例|
|:--|:--|:--|
|ボキャブラリー|メタ情報を定義するもの|schema.org|
|シンタックス|メタ情報の記述方法|Microdata、RDFa、JSON-LD|

ボキャブラリーで定義されているものについてシンタックスの仕様通りに記述することで、はじめて「構造化データ」として認識されるということです。

「JSON-LD」は上記の通り、シンタックスの1種類でGoogleが推奨するものです。

## 「JSON-LD」の記述方法

今回は「JSON-LD」形式での「構造化データ」の作成方法を紹介します。

```
<script type="application/ld+json">
  {
    "@context":"http://schema.org",
    "@type":"BreadcrumbList",
    "name":"パンくずリスト",
    "itemListElement":[
      {
        "@type":"ListItem",
        "position":1,
        "item":{"name":"HOME","@id":"https://sample.com/"}
      },
      {
        "@type":"ListItem",
        "position":2,
        "item":{"name":"Sub-Page","@id":"https://sample.com/subpage/"}
      }
    ]
  }
</script>
```

「JSON-LD」形式での構造化データは上記のようなマークアップで記述することが可能です。
しかし、各投稿に上記のコードを登録する作業は正直面倒です。修正する場合にも、各投稿に登録したコードを手動で修正しなくてはいけないため、修正に時間がかかります。
そこで、WordPressのテーマ内の`functions.php`に関数を追加し、`wp_head`にフックさせることで「JSON-LD」形式での構造化データを自動生成し出力させたいと思います。

## 「JSON-LD」形式のパンくずリストの自動生成関数

下記のコードをテーマ内の`functions.php`に記載いただければ`<head>`内に「JSON-LD」形式での構造化データが自動で生成されます。
なお、下記のコードは基本コードです。実装する際には、**実装するサイトの仕様や要望にあわせてカスタマイズして利用**してください。

```
function json_breadcrumb() {
 
  if( is_admin() || is_home() || is_front_page() ){ return; }
 
  $position  = 1;
  $query_obj = get_queried_object();
  $permalink = ( empty($_SERVER["HTTPS"] ) ? "http://" : "https://") . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
 
  $json_breadcrumb = array(
    "@context"        => "http://schema.org",
    "@type"           => "BreadcrumbList",
    "name"            => "パンくずリスト",
    "itemListElement" => array(
      array(
        "@type"    => "ListItem",
        "position" => $position++,
        "item"     => array(
          "name" => "HOME",
          "@id"  => esc_url( home_url('/') ),
        )
      ),
    ),
  );
 
  if( is_page() ) {
 
    $ancestors   = get_ancestors( $query_obj->ID, 'page' );
    $ancestors_r = array_reverse($ancestors);
    if ( count( $ancestors_r ) != 0 ) {
      foreach ($ancestors_r as $key => $ancestor_id) {
        $ancestor_obj = get_post($ancestor_id);
        $json_breadcrumb['itemListElement'][] = array(
          "@type"    => "ListItem",
          "position" => $position++,
          "item"     => array(
            "name" => esc_html($ancestor_obj->post_title),
            "@id"  => esc_url( get_the_permalink($ancestor_obj->ID) ),
          )
        );
      }
    }
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => esc_html($query_obj->post_title),
        "@id"  => esc_url($permalink),
      )
    );
 
  } elseif( is_post_type_archive() ) {
 
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => $query_obj->label,
        "@id"  => esc_url( get_post_type_archive_link( $query_obj->name ) ),
      )
    );
 
  } elseif( is_tax() || is_category() ) {
 
    if ( !is_category() ) {
      $post_type = get_taxonomy( $query_obj->taxonomy )->object_type[0];
      $pt_obj    = get_post_type_object( $post_type );
      $json_breadcrumb['itemListElement'][] = array(
        "@type"    => "ListItem",
        "position" => $position++,
        "item"     => array(
          "name" => $pt_obj->label,
          "@id"  => esc_url( get_post_type_archive_link($pt_obj->name) ),
        )
      );
    }
 
    $ancestors   = get_ancestors( $query_obj->term_id, $query_obj->taxonomy );
    $ancestors_r = array_reverse($ancestors);
    foreach ($ancestors_r as $key => $ancestor_id) {
      $json_breadcrumb['itemListElement'][] = array(
        "@type"    => "ListItem",
        "position" => $position++,
        "item"     => array(
          "name" => esc_html( get_cat_name($ancestor_id) ),
          "@id"  => esc_url( get_term_link($ancestor_id, $query_obj->taxonomy) ),
        )
      );
    }
 
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => esc_html( $query_obj->name ),
        "@id"  => esc_url( get_term_link($query_obj) ),
      )
    );
 
  } elseif( is_single() ) {
 
    if ( !is_single('post') ) {
      $pt_obj = get_post_type_object( $query_obj->post_type );
      $json_breadcrumb['itemListElement'][] = array(
        "@type"    => "ListItem",
        "position" => $position++,
        "item"     => array(
          "name" => esc_html( $pt_obj->label ),
          "@id"  => esc_url( get_post_type_archive_link($pt_obj->name) ),
        )
      );
    }
 
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => esc_html( $query_obj->post_title ),
        "@id"  => esc_url($permalink),
      )
    );
 
  } elseif( is_404() ) {
 
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => "404 Not found",
        "@id"  => esc_url($permalink),
      )
    );
 
  } elseif( is_search() ) {
 
    $json_breadcrumb['itemListElement'][] = array(
      "@type"    => "ListItem",
      "position" => $position++,
      "item"     => array(
        "name" => "「" . get_search_query(). "」の検索結果",
        "@id"  => esc_url($permalink),
      )
    );
 
  }
 
  echo '<script type="application/ld+json">'.json_encode( $json_breadcrumb, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ).'</script>';
}
 
add_action( 'wp_head', 'json_breadcrumb' );
```

## 注意事項

上記のコードには以下の注意点があります。

- 「タグアーカイブ」には対応していません。
- 「日付別アーカイブ」には対応していません。
- 「著者別アーカイブ」には対応していません。
- 「ファイルページ」には対応していません。
- デフォルトの「投稿」の情報は表示しない仕様にしています。

理由として、サイト制作において使用しない、または使用頻度がとても低いという点があります。
上記のページにも実装したい場合でも、その他のコードを見ながら追記すれば決して難しくないかと思います。

## 参考

- [Google検索セントラル > ドキュメント > 上級者向け SEO > パンくずリスト](https://developers.google.com/search/docs/advanced/structured-data/breadcrumb?hl=ja)