最もアクセシブルで、最もセマンティックで、そして最もリッチ(リザルト)なパンクズの実装方法でいかせていただきます。

## はじめに
みなさん、パンクズリストって作ったことありますか？
何階層にもわたる規模のサイトならばほぼ100%出現するUIですが
普段みなさんはどうやって実装しているでしょうか。

```html
<ol>
    <li><a href="/">ホーム</a></li>
    <li><a href="/hogehoge/">hogehoge</a></li>
    <li>fuga</li>
</ol>
```
こんな感じでとりあえずol>liで組んでおけばええでしょ！

ではまだ足りません。

この記事では
ここまでやっときゃかなり良さげでは？に たどり着いたパンクズ実装方法をご紹介します。


## とりあえずパンクズの役割を整理

1. ナビゲーションの補助
1. ユーザビリティの向上
1. SEO効果
1. コンテンツの文脈理解

こんなところでしょうか。

それぞれ簡単に解説します。

### ナビゲーションの補助
パンクズはユーザーが現在どのページにいるのか、サイト構造内での位置を視覚的に示します。
これにより、ユーザーが簡単に上位階層や関連するカテゴリーページに戻ることができます。

例:
ホーム > 商品 > カテゴリー > 商品名

### ユーザビリティの向上
特に階層構造が深いサイトで、ユーザーが目的のページに移動する際の操作を簡単にできます。
これにより、サイト内で迷子になることを防ぎ、ストレスのない閲覧体験を与えることができます。

### SEO効果
- 検索エンジンはパンクズを通じて、サイトの構造や階層を理解しやすくなります
- パンクズを適切にマークアップすることで、Googleの検索結果にリッチスニペットとして表示され、クリック率の向上を見込めるかもしれません

### コンテンツの文脈理解
ユーザーにページのカテゴリやトピックを直感的に理解させ、関連情報へのアクセスを促します。
特に新規ユーザーや、深めの階層に突然アクセスしたユーザーにとって有効だと思います。

----

はい
ちゃんとやろうとすると結構考えることがありそうです。

ではどうやって実装していけばよいのか
ステップを踏んで解説していきます。

## 1. まずはセマンティックに
冒頭で登場したHTML
```html
<ol>
    <li><a href="/">ホーム</a></li>
    <li><a href="/hogehoge/">hogehoge</a></li>
    <li>fuga</li>
</ol>
```
こいつを進化させていこうと思います。

まず`ol`を`nav`でラップしましょう。
こいつはナビゲーションの役割なので。

```html
<nav>
  <ol>
    <li><a href="/">ホーム</a></li>
    <li><a href="/hogehoge/">hogehoge</a></li>
    <li>fuga</li>
  </ol>
</nav>
```
ほいで、必要な aria 属性とその値を付与します。

`aria-label="Breadcrumb"`
nav 要素で提供される、ナビゲーションの種類を説明するラベルを提供します。

`aria-current="page"`
ナビゲーショングループの最後のリンクに適用され、現在のページを表すことを示します。

```html
<nav aria-label="Breadcrumb">
  <ol>
    <li><a href="/">ホーム</a></li>
    <li><a href="/hogehoge/">hogehoge</a></li>
    <li><a href="" aria-current="page">fuga</a></li>
  </ol>
</nav>
```

最後のリンク「fuga」は、リンクである必要はなさそうですが**a要素でマークアップ**します。
この理由は、**読み上げ技術支援者に対応するため**です。

a要素でマークアップすれば
タブキーで移動した時に**フォーカスが当たるように**なります。
フォーカスが当たればその内容が読み上げられます。

そして、aria-current="page"を付与していることによって「現在のページ」と読み上げられます。

![スクリーンショット 2024-12-05 17.46.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2341756/75ec3b6b-667d-8a14-4f7d-fa2caa3ead26.png)

そして、aria-current="page"はa要素に付与するのが一般的です。
したがって、リンクである必要はないですが、a要素でマークアップする必要は十分にあるということです。

## スタイリングで気をつけるべきこと
パンクズには、リンクとリンクの間に装飾があることが結構ありますよね。

スラッシュだったり
![スクリーンショット 2024-12-05 17.51.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2341756/dc0a1923-34d1-dfa8-7218-f18242fb873e.png)

中黒だったり
![スクリーンショット 2024-12-05 17.52.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2341756/8fa7fad4-feb6-cece-7120-0abec3319f4a.png)

このとき、リンク間の装飾は**必ずCSSで表現しましょう。**
`span`とかで装飾用のHTMLを含めてはいけないということです。


<p class="codepen" data-height="300" data-default-tab="css,result" data-slug-hash="EaYPNPa" data-pen-title="Untitled" data-editable="true" data-user="lreeyied-the-decoder" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lreeyied-the-decoder/pen/EaYPNPa">
  Untitled</a> by 加藤菜摘 (<a href="https://codepen.io/lreeyied-the-decoder">@lreeyied-the-decoder</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

このCodepenではスラッシュは疑似要素としていれてます。
こうすることで、パンクズが読み上げられた時、装飾まで読み上げられてしまうことを防ぎます。

### 仕上げ
このままでは現在のページである「fuga」もクリック可能になってしまいます。クリックしてもどこにも行かないのに。

なので、aria-current属性が付いているa要素には以下のCSSを当ててあげましょう
```scss

  a {
    &[aria-current="page"] {
      color: #000;
      text-decoration: none;
      pointer-events: none;
    }
  }
```


こうすることで、クリックできない通常テキストとして扱いつつ、フォーカス可能で正しく読み上げられる、最もアクセシブルで、最もセマンティックなパンクズができあがりました。

## SEO対策

では、SEO対策もしてみましょう。

Googleで検索すると
![スクリーンショット 2024-12-06 11.03.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2341756/aba7bcfb-a2ba-5158-106e-f196e612c5f6.png)

こんな感じで下層ページへのリンクがリストとして表示されているのをよく見ると思います。

これは「リッチリザルト」と呼ばれ、上の画像はパンクズのリッチリザルトです。
検索結果がこうなってるとそのサイトのクリック率が上がると言われています。

リッチリザルトを表示させるには**構造化データ**というものを仕込んでおく必要があります。

Googleのクローラーは優秀なので、正直今まで説明したマークアップをしておけば勝手にリッチリザルトとして表示してくれたりします。
が、階層構造が正しくなかったりと、正しい・期待通りのリッチリザルトにするためにはやはり構造化データを仕込んでおくことに越したことはありません。

### 構造化データってなんやねん
Googleの公式ドキュメントに全てが書いてあります。
https://developers.google.com/search/docs/appearance/structured-data/search-gallery?hl=ja

もちろんリッチリザルトはパンクズだけでなく、いろーーーーんな種類があるので
リッチリザルトとして表示したいものはあるかというのは事前に確認しておきましょう。

構造化データは`JSON-LD`という形式で、主にHTMLのheadタグ内に置いておきます。（bodyでもいいらしいけど）
`JSON-LD`なんて聞いたことないと言う人もいるかもしれませんが安心してください。ほぼ`json`です。

Googleがテンプレを置いといてくれてるのでありがたく使わせてもらいましょう

```html
    <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>
```
これがGoogleが用意してくれてるパンクズリストの構造化データのテンプレです。
https://developers.google.com/search/docs/appearance/structured-data/breadcrumb?hl=ja

このテンプレをいじいじするのですが
変えるべきところは
`position`、`name`、`item`の3つだけです。

`position`はサイトにおいて何階層目にあたるページか
`name`はそのページタイトル。パンクズリストとしてマークアップしているページ名と同じにするのが望ましそうです。
`item`はそのページのURL
です。

これをパンクズリストがある全ページに仕込んでおきましょう！！！

....


いや、全ページ手動で追加するのだるすぎる


### 構造化データはJSで生成しても問題ないの？
はい、そうなったらJSで自動生成させたくなりますよね。

でも、JSってクライアントサイドで発火するものだから、Googleのクロールのタイミングによっては構造化データがまっさらの状態をクロールしちゃって意味ないんじゃないの？

と思ったそこのあなた

たぶん大丈夫です。

GoogleのドキュメントにJSで構造化データを生成する人にむけたドキュメントがあります。
https://developers.google.com/search/docs/appearance/structured-data/generate-structured-data-with-javascript?hl=ja#custom-javascript

だし、実際にJSで生成した構造化データがあるページをリッチリザルトテストにかけたらしっかり認識してくれました。

:::note info
ただし、構造化データが検索結果に必ず表示されるとは限りません。リッチリザルトテストに沿ってページを正しくマークアップしても表示されない場合があります。
:::

クロールの詳しい仕様やタイミングについてはGoogleの人以外誰もわからないです。
どんなに正しくマークアップしていても、構造化データをしっかり仕込んでいても、必ずしもリッチリザルトとして表示されるとは限らないので、実際に指摘された時に焦らないよう覚えておきましょう。

### JSで構造化データを生成するスニペット
最後にスニペットを置いておきます。
誰かの役に立て〜〜〜〜；；

```html
<nav class="c-breadcrumb" aria-label="Breadcrumb">
  <ol class="js-breadcrumb_list">
    <li>
      <a href="/" class="js-breadcrumb_link" data-position="1">
      <span class="c-breadcrumb_txt">ホーム</span>
      </a>
    </li>
    <li>
      <a href="/hogehoge/" class="js-breadcrumb_link" data-position="2">
      <span class="c-breadcrumb_txt">hogehoge</span>
      </a>
    </li>
    <li>
      <a href="" aria-current="page" class="js-breadcrumb_link" data-position="3">
      <span class="c-breadcrumb_txt">fuga</span>
      </a>
    </li>
  </ol>
</nav>
```

```js
const breadcrumbData = () => {
  const breadcrumbListElem = document.querySelector('.js-breadcrumb_list');
  if(!breadcrumbListElem) return;
  const breadcrumbListItemElem = breadcrumbListElem.querySelectorAll('.js-breadcrumb_link');
  const breadcrumbItems = [];

  breadcrumbListItemElem.forEach(el => {
    const breadcrumbItem = {
      '@type': 'ListItem',
      position: el.dataset.position,
      name: el.querySelector('.c-breadcrumb_txt').innerText,
      item: el.href ? el.href : '',
    };
    breadcrumbItems.push(breadcrumbItem);
  });

  const breadcrumbList = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: breadcrumbItems
  };

  const jsonLD = JSON.stringify(breadcrumbList);

  const scriptElement = document.createElement('script');
  scriptElement.type = 'application/ld+json';
  scriptElement.textContent = jsonLD;

  document.head.appendChild(scriptElement);
};
```
`position`の値はカスタムdata属性を持たせてJSで取得します。
ぶち込み先は`head`タグ内の一番最後です。

以上です！

## 参考URL
- https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/examples/breadcrumb/
- https://developers.google.com/search/docs/appearance/structured-data?hl=ja