Shopifyの自作パンくずでファセット(絞り込み)の表示に対応する

Shopifyの自作パンくずでファセット(絞り込み)の表示に対応する

Shopifyでアプリを使わずにパンくずを実装する手段として、LiquidのコードのサンプルはShopifyパートナーのブログなどでも多数紹介されています。
また、オンラインストアのメニューを使用する方法も公式で紹介されていますが、この記事ではコレクションページで絞り込みをした際の現在地の再レンダリングや、ブログなどのタグ絞り込みページでの現在地の出力なども含めてご紹介します。

パンくずを表示するかどうかの判定

サイトの設計にもよりますが、トップページなどそもそもパンくずを表示する必要がないページがあったりします。
そこで一番はじめにそれらを除外するためのunless文を書いてしまいます。

ここでは例として、サイトトップ、アカウント関連、パスワード認証画面、カートページを除外しています。

{%- comment -%}アカウント関連ほか、パンくずを表示しないページを除外{%- endcomment -%}
{%- unless
 template == 'index' or request.path contains '/account' or request.path contains '/challenge' or request.path contains '/cart'
 -%}
...略...
{%- endunless -%}

この中に、順番にリンクを書き出していきます。

トップページのリンクが必要なページすべてにリンクを表示

まず必須なのは、パンくずリストを構成するHTML(例ではnav要素とul要素で構成しています)の開始タグですね。
それから、除外されていない下層ページすべての親となるトップページのリンクも最初に出力したいです。

  <nav id="breadcrumbs" class="page-wide">
    <ul class="breadcrumbs">
      <li>
        <a href="/">トップ</a>
      </li>
...略...
    </ul>
  </nav>

商品関連のページすべてに商品一覧のリンクを表示

次に、コレクションページや商品ページなど、商品に関するページには、「すべての製品一覧」にあたるページである「/collections/all」へのリンクを用意したいです。

      {%- comment -%}コレクション・商品ページ判定{%- endcomment -%}
      {%- if product or collection -%}

          <li>
            <a href="/collections/all">すべての製品一覧</a>
          </li>
...略...
      {%- endif -%}

コレクション・商品ページのパンくず

ここからいよいよメインとなる商品関連のパンくずを作っていきます。
大掛かりになるのでコードはバックアップをとりながら少しずつ実装していきましょう!

Shopify管理画面から階層化されたリンクリストを取得する

リンクリストの公式の解説はこちらです。

カテゴリ階層の概念がないShopifyで擬似的にカテゴリ階層を作るための方法は、サイドメニューを作るときの記事でご紹介しました。

作成した3階層メニューを使用して、コレクション〜商品ページのパンくずとして応用していきます。

        {%- comment -%}メニュー:商品メニュー(directories-menu)から自動生成{%- endcomment -%}
        {%- assign menu = linklists.directories-menu -%}
        {%- for directory1 in menu.links -%}
          {%- if directory1.active or directory1.child_active -%}
            <li>
              <a href="{{ directory1.url }}">{{ directory1.title }}</a>
            </li>
          {%- endif -%}
          {%- for directory2 in directory1.links -%}
            {%- if directory2.active or directory2.child_active -%}
              <li>
                <a href="{{ directory2.url }}">{{ directory2.title }}</a>
              </li>
            {%- endif -%}
            {%- for directory3 in directory2.links -%}
              {%- if directory3.active -%}
                <li>
                  <a href="{{ directory3.url }}">{{ directory3.title }}</a>
                </li>
              {%- endif -%}
            {%- endfor -%}
          {%- endfor -%}
        {%- endfor -%}

もとのサンプルコードはメニューのアイテムを階層ごとに書き出してナビゲーションを作っているため、登録されたリンクをすべて表示しています。
現在地のページがこのリンクリストに含まれているかどうかを判定するためにこの仕組みを利用しています。

まずfor文でリンクリストの1階層めを順に見ながら「現在のリストアイテムのページがアクティブ」もしくは「現在のリストアイテムのひとつ下の階層がアクティブ」であるかどうかを判定します。

          {%- if directory1.active or directory1.child_active -%}
            <li>
              <a href="{{ directory1.url }}">{{ directory1.title }}</a>
            </li>
          {%- endif -%}

現在地が子階層のページの場合にも、パンくずとしてはひとつ上の階層のリンクも表示したいですから、このような条件になります。
あとは同様に、2階層め、3階層めもfor文でまわしているだけです。

タグを利用する場合

私のサンプルはゲームソフト販売サイトなので、異なるメーカー間で「PS5」や「Switch」といった同じ絞り込み条件をかけあわせたリンクが必要になります。
しかし、「メーカーx対応機種」のすべての組み合わせのコレクションを作るのは大変です。

メタフィールドではなく商品のタグに対応機種やジャンルを登録しておけば、メニュー側でメーカーのコレクションをリンクとして設定し、タグを指定することで「メーカーx対応機種」「メーカーxジャンル」などで絞り込んだタグページをリンクとして設定することができます。
(商品タイプやメタフィールドの絞り込みもできればいいのになぜタグだけなのでしょう…)

上記の方法で現在地がタグページでもパンくずを出力することは可能なのですが、
注意が必要な点は、

  • タグそのものにタグ同士の親子関係を紐付けすることができるわけではない
  • 対応機種のタグ、ジャンルのタグといったグルーピングができるわけではない
  • 3階層目は「PS5, アクション」のように親階層タグと複合タグにする必要がある(半スペあり。「PS5+アクション」と入力した場合は保存時にカンマ区切りに変換されます)
  • 複合絞り込みのリンクは「PS5,アクション」でアクセスしたときにパンくずが反映されない

というところです。

ちなみにURLの複合タグを「+」にするか「,」にするかの違いは、一応公式チュートリアルでは「+」です。どっちでも絞り込みしたページが表示されます。
実際カンマ区切りのURLが存在してしまう、アクセスできてしまうので、このときにパンくずがきれいに表示されなくなってしまうことを許容するかどうかが判断のポイントになるかなと思います。

このあたりは商材やサイトの導線設計によって、どれが最適かは変わってくるかと思います。
私が実務で携わっているサイトのカテゴライズだと、別軸のカテゴリをまたぐカテゴリがないためコレクションのみで親子関係が作れるのですが、今回のサンプルサイトで実際に複合するケースにあたって、コレクションだけだと実現が難しい部分もあるなあと痛感しました。

ついでなのでカンマ区切りのURLを直接リンクとして指定したメニューを追加する方法も試しましたがうまくいきませんでした。(そもそも日本語タグはエンコードされてしまう)
なお、タグではなくファセットで絞り込みをした状態のコレクションのURLを登録する方法もうまくいきません。
あくまでも現在地のURLがリンクリストに含まれるかどうかを判定するので、パラメータのないURLや、ソートやその他の絞り込みパラメータが入ったURLにアクセスした場合には同一URLと看做されず、パンくずに反映されなくなってしまいます。
おとなしくコレクションで対応するか、カンマ区切りURLの存在を無視して複合タグオプションで対応するしかないかも。

3階層までの制限と商品ページのリンクをどうするか問題

さて、パンくずの階層をメニューをループさせて実現する方法を紹介している記事などを見ながら試していて、商品ページのパンくずがどうも期待したとおりにならないな?と感じた人はいませんか?
私は「そういうことか〜、そういうことじゃないんだよな〜」となりました。

擬似的に商品カテゴリを実現する方法としてこのメニューを使用しているので、ほとんどの場合はリンクリストに設定しているのは何らかの条件で商品をまとめた「コレクション」だと思われます。(または前項の説明のようにタグページだったりします)
すると期待するのは、現在地がこのコレクションに含まれている商品だった場合、ついでに当該ページのタイトルなども自動で表示してほしいです。

ところが、この方法は、あくまでもメニューに登録されたリンクがアクティブかどうか、リンクリストの中の子階層のリンクがアクティブかどうかを判定するものなので、当該ページがリンクリストの値になっているコレクション内のアイテムなのかどうかは判定できないのです。

もちろんメニューに個別に商品を登録していけば実現はできますが、とても骨が折れますし、メニューは3階層までしか作ることができないため、商品ページを3階層めに設定してしまうと親カテゴリには2階層分しか使えません。

難しく考える必要はなく、商品ページが現在地なら、それを判定して直接出力してしまえば話は簡単です。

{%- if product -%}
          <li>
            <a href="{{ product.url }}">{{ product.title }}</a>
          </li>
{%- endif -%}

ちなみに、Liquidのlink_toフィルタを使うことで下記のように記述することもできます。
(私はコードを見た時にHTMLの構造がわかりやすいように、a要素をそのまま記述しています。プロダクトのコーディングルールがある場合はそれに合わせましょう)

{{ product.title | link_to: product.url }}

さて、ここで問題なのは、この商品ページはメニューに含まれていないため、親階層にあたるコレクションのリンクは表示されません。

私がサンプルで作っているゲームソフト販売サイトの場合は、「メーカー(販売元)」と「ゲームのジャンル(商品タイプ)」という2つの軸のカテゴライズがあります。
たとえば、「メーカー > ジャンル > 商品」という階層でパンくずを表示したい場合には、下記のようにします。

        {%- comment -%}商品ページ{%- endcomment -%}
        {%- if product -%}
          {%- assign collection_vendor = product.vendor -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_vendor -%}
              <li>
                <a href="{{ collection.url }}">{{ collection.title }}</a>
              </li>
            {%- endif -%}
          {% endfor %}
          {%- assign collection_type = product.type -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_type -%}
              <li>
                <a href="{{ collection.url }}">{{ collection.title }}</a>
              </li>
            {%- endif -%}
          {% endfor %}
          <li>
            <a href="{{ product.url }}">{{ product.title }}</a>
          </li>
        {%- endif -%}

販売元と商品タイプを条件にした自動コレクション名と、商品に登録されている販売元名と商品タイプ名を照らし合わせて、当てはまるコレクションがあったらそのコレクションのURLとタイトルを引っ張ってきます。
上記の商品タイプコレクションのリンクは販売元x商品タイプのページではないことに注意してください。
そういうふうにしたい場合は、商品タイプをパラメータにしたURLを設定するだけです。

          {%- assign collection_vendor = product.vendor -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_vendor -%}
              <li>
                <a href="{{ collection.url }}">{{ collection.title }}</a>
              </li>
              {%- assign vendor_url = collection.url -%}
            {%- endif -%}
          {% endfor %}
          {%- assign collection_type = product.type -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_type -%}
              {%- assign type_url = vendor_url | append: '?filter.p.product_type=' | append: collection_type -%}
              <li>
                <a href="{{ type_url }}">{{ collection.title }}</a>
              </li>
            {%- endif -%}
          {% endfor %}

ここまでで、トップページから商品ページまでのパンくずを表示することができました。

トップ > 製品一覧 > カテゴリ1 > カテゴリ2 > (カテゴリ3>) 商品ページ

タグで管理している場合の悩みポイントとサイトマップ設計について考える脱線コーナー

もしすべての情報をタグで管理している場合はここでちょっとつまづくと思います。
現在地の商品が持っているタグのうち、どれが「対応機種」でどれが「ジャンル」なのか、親階層になるタグを判定できないからです。

対応機種別に商品ページが存在している場合は親階層を「PS5」や「Switch」と断定する材料があるので、
メタフィールド管理ならば直接それを指定、タグ管理なら対応機種タグの配列か何かを参照してヒットしたらそのタグを出力するなど、なにかしら実装のやりようはあります。

ゲームソフト1タイトルの商品の中に対応機種のSKUが存在している場合、ユーザーがどのハードを求めているかを判断する術がないので、親階層をどの機種にするのか判断することができません。
(対応機種から辿ってきた場合には履歴から判定して出すこともできそうですが、ゴリゴリにフロントの実装をする必要がありますね…)

トップ > すべての製品一覧 > メーカー > PS5 > アクション > ソフト名

みたいな表示は諦めて、

トップ > すべての製品一覧 > メーカー > アクション > ソフト名

と、してしまう…とかでしょうか?

PS5で遊べるソフトを探しているのか、アクションゲームを探しているのか、というユーザーの動機によって、戻りたい一覧ページは変わってきますし、なんなら複合して絞り込まれていないジャンル一覧に戻りたいかもしれません。
あくまでもゲームソフトのパンくず、という点で情報を整理するなら、対応機種を列記する手もあるかもしれません。

トップ > すべての製品一覧 > アクション > PS5, PS4, Switch > ソフト名

「PS5」をクリックしたら「アクション+PS5」タグページに遡れる

メーカーはそもそもゲームソフトから遡るための階層としては持たないで、まあそのあたりはナビゲーションにまかせると割り切る。

ほかにも、親カテゴリごとにパンくずを複数行表示するようなECサイトもありますね。

トップ > すべての製品一覧
> アクション > ソフト名
> PS5, PS4, Switch > ソフト名
> メーカー > ソフト名

サイトマップ設計ってめちゃくちゃ大変だけど考えるの楽しいですね。

絞り込み条件に応じて現在地のテキストを書き換える

このままでも十分ではあるのですが、ちょっと欲が出てきちゃったのでこんなこともしちゃいましょう。

コレクションページで、たとえば「アクション」「RPG」といったジャンルで絞り込みをした場合、ファセットの状態に応じてパンくずも変更してあげたいです。


        {%- comment -%}コレクションページ(商品リスト)で絞り込みをしている場合の条件{%- endcomment -%}
        {%- liquid 
          assign facets_active = blank
          for filter in collection.filters
            for value in filter.active_values
              assign facets_active = facets_active | append: '<span class="facets-value">"' | append: value.label | append: '"</span>'
            endfor
          endfor
        -%}
        {%- if facets_active != blank -%}
          <li>{{ facets_active }}の絞り込み結果</li>
        {%- endif -%}

まず絞り込みがされている状態かどうかを判定するためにfacets_activeという変数を用意しました。(変数名は任意でOK)
次にコレクションのフィルターをfor文でまわして、現在アクティブなフィルターのフィルター名を取得します。
最初に用意しておいたfacets_activeに、取得したフィルター名をappendで追加していきます。
最後に、絞り込み項目が空でなかったらその項目名を出力します。

わざわざspan要素で囲んでいるのは、あとでCSSで項目ごとに区切りをつけるためなのですが、ここで直接区切り文字を入れても構いません。(デザインによるので)

いや〜、商品関連だけでけっこうかかりましたね。

検索画面のパンくず

せっかくなので、検索画面でもパンくずに絞り込みキーワードを表示しましょう。ついでに、件数も出しちゃいましょう。
商品関連ページが終わったので、このあとはelsifで各ページの条件を分岐していきます。

        {%- comment -%}検索結果{%- endcomment -%}
      {%- elsif template contains 'search' -%}
        <li>
          <a href="/search">検索する</a>
        </li>
        {%- if search.terms != blank -%}
          <li>
            <a href="{{ link.current }}">
              "{{ search.terms }}" の検索結果|{{ search.results.size }}
            </a>
          </li>
        {%- endif -%}

もしも検索条件(キーワード)が空でない場合には、link.currentで現在の検索結果ページのURLを表示します。
そしてsearch.termsで検索キーワードをパンくずに入れてあげます。
search.results.sizeで検索結果の件数も出せます。

実はここの実装中、search.results_countだと件数が合わないという問題に直面しまして、search.results.sizeで対応しています。
軽く調べてみると似たようなことで困ってる方がいて、どうやらresults_countは意図した値が出てこないようです。

私が実務でつまずいたときも、検索キーワードの対象になる商品を過去に削除したりしていたので、なんとなく、過去にDBに該当するデータがあった場合(論理削除されたデータ?)、それらの数が含まれてしまっていそうな気がします。

ブログのパンくず

ブログの場合はわりとシンプルです。
ブログトップのリンク、もしタグページにいる場合にはタグのリンク、そして記事ページにいるときは記事のリンクを順に表示するだけです。

        {%- comment -%}ブログ{%- endcomment -%}
      {%- elsif blog -%}
        <li>
          <a href="{{ blog.url }}">{{ blog.title }}</a>
        </li>
        {%- if current_tags -%}
          <li>
            <a href="{{ current_tags.url }}">{{ current_tags }}</a>
          </li>
        {%- endif -%}
        {%- if article -%}
          <li>
            <a href="{{ article.url }}">{{ article.title }}</a>
          </li>
        {%- endif -%}

ページのパンくず

WordPressの固定ページにあたる、pagesページ(ややこしいので何か区別する呼び名がほしいです)のパンくずを設定します。
どのページも単純にページタイトルをそのまま表示するのであればこれだけで済みます。

          <li>
            <a href="{{ page.url }}">{{ page.title }}</a>
          </li>

しかし、ページはそれぞれ個別のテンプレートをあてたりしていることもあります。
case文でtemplateの値を判定して対応していきます。
私の場合は404ページのタイトルを変えて、パンくず上では「ページが見つかりません」という文言にしたかったのでこんな感じにしました。

        {%- comment -%}ページ個別設定{%- endcomment -%}
      {%- elsif page -%}
        {%- case template -%}
          {%- when '404' -%}
          <li>
            <a href="{{ page.url }}">ページが見つかりません</a>
          </li>
          {%- comment -%}その他の汎用ページ{%- endcomment -%}
        {%- else -%}
          <li>
            <a href="{{ page.url }}">{{ page.title }}</a>
          </li>
        {%- endcase -%}

完成コード

ここまでのコードをすべてまとめると、下記のような感じです。
これをbreadcrumbs.liquidみたいなスニペットファイルにして、theme.liquidなどから呼び出せばOK。ですが…これでは終わりません。



{%- comment -%}アカウント関連ほか、パンくずを表示しないページを除外{%- endcomment -%}
{%- unless
 template == 'index' or request.path contains '/account' or request.path contains '/challenge' or request.path contains '/cart'
 -%}

  <nav id="breadcrumbs" class="page-wide">
    <ul class="breadcrumbs">
      <li>
        <a href="/">トップ</a>
      </li>

      {%- comment -%}コレクション・商品ページ判定{%- endcomment -%}
      {%- if product or collection -%}

          <li>
            <a href="/collections/all">すべての製品一覧</a>
          </li>

        {%- comment -%}メニュー:商品メニュー(directories-menu)から自動生成{%- endcomment -%}
        {%- assign menu = linklists.directories-menu -%}
        {%- for directory1 in menu.links -%}
          {%- if directory1.active or directory1.child_active -%}
            <li>
              <a href="{{ directory1.url }}">{{ directory1.title }}</a>
            </li>
          {%- endif -%}
          {%- for directory2 in directory1.links -%}
            {%- if directory2.active or directory2.child_active -%}
              <li>
                <a href="{{ directory2.url }}">{{ directory2.title }}</a>
              </li>
            {%- endif -%}
            {%- for directory3 in directory2.links -%}
              {%- if directory3.active -%}
                <li>
                  <a href="{{ directory3.url }}">{{ directory3.title }}</a>
                </li>
              {%- endif -%}
            {%- endfor -%}
          {%- endfor -%}
        {%- endfor -%}
        {%- comment -%}コレクションページ(商品リスト)で絞り込みをしている場合の条件{%- endcomment -%}
        {%- liquid 
          assign facets_active = blank
          for filter in collection.filters
            for value in filter.active_values
              assign facets_active = facets_active | append: '<span class="facets-value">"' | append: value.label | append: '"</span>'
            endfor
          endfor
        -%}
        {%- if facets_active != blank -%}
          <li>{{ facets_active }}の絞り込み結果</li>
        {%- endif -%}

        {%- comment -%}商品ページ{%- endcomment -%}
        {%- if product -%}
          {%- assign collection_vendor = product.vendor -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_vendor -%}
              <li>
                <a href="{{ collection.url }}">{{ collection.title }}</a>
              </li>
              {%- assign vendor_url = collection.url -%}
            {%- endif -%}
          {% endfor %}
          {%- assign collection_type = product.type -%}
          {% for collection in collections %}
            {%- if collection.title contains collection_type -%}
              {%- assign type_url = vendor_url | append: '?filter.p.product_type=' | append: collection_type -%}
              <li>
                <a href="{{ type_url }}">{{ collection.title }}</a>
              </li>
            {%- endif -%}
          {% endfor %}
          <li>
            <a href="{{ product.url }}">{{ product.title }}</a>
          </li>
        {%- endif -%}

        {%- comment -%}検索結果{%- endcomment -%}
      {%- elsif template contains 'search' -%}
        <li>
          <a href="/search">検索する</a>
        </li>
        {%- if search.terms != blank -%}
          <li>
            <a href="{{ link.current }}">
              "{{ search.terms }}" の検索結果|{{ search.results.size }}
            </a>
          </li>
        {%- endif -%}

        {%- comment -%}ブログ{%- endcomment -%}
      {%- elsif blog -%}
        <li>
          <a href="{{ blog.url }}">{{ blog.title }}</a>
        </li>
        {%- if current_tags -%}
          <li>
            <a href="{{ current_tags.url }}">{{ current_tags }}</a>
          </li>
        {%- endif -%}
        {%- if article -%}
          <li>
            <a href="{{ article.url }}">{{ article.title }}</a>
          </li>
        {%- endif -%}

        {%- comment -%}ページ個別設定{%- endcomment -%}
      {%- elsif page -%}
        {%- case template -%}
          {%- when '404' -%}
          <li>
            <a href="{{ page.url }}">ページが見つかりません</a>
          </li>
          {%- comment -%}その他の汎用ページ{%- endcomment -%}
        {%- else -%}
          <li>
            <a href="{{ page.url }}">{{ page.title }}</a>
          </li>
        {%- endcase -%}

    {%- else -%}
      {%- endif -%}
    </ul>
  </nav>

{%- endunless -%}

ここまでで実際に試してみていただくと、コレクションページのファセット絞り込み項目が、うまく表示されないことがわかると思います。

次項で解決していきましょう。

ファセットを変更したときにパンくずの表示も更新したい

問題点は、コレクションページでファセットの絞り込み条件を変更した際に、パンくずがそのままになってしまうことです。
とくにパラメータをもたないコレクションページのURLにアクセスした場合は、コレクションページのタイトルが表示されているだけなのでそこまで実害はありません。
ところが、ファセットの絞り込み条件のパラメータをもったURLにアクセスした場合は、最初からパンくずに絞り込み条件が表示された状態です。そこでファセット条件を変更しても、パンくずの条件はそのままになってしまいます。
そうするとページ上で表示している内容とパンくずの内容が異なってしまうので、ユーザーを混乱させてしまいます。

そこで、この動画のようにファセットの変更に応じてパンくずの表示も反映させたいと思います!

Shopifyテーマの仕組みでは、Section Rendering APIというAPIが利用できます。
セクションIDのパラメータをつけたURLでAPIを叩くことでそのセクションをレンダリングすることが可能です。

Dawnテーマのfacets.jsには、もともとそのレンダリングの処理が書かれている箇所がありますので、それを真似して実装してしまいましょう。

パンくずのスニペットをセクションに変更する

そのために、まずはパンくずのファイルをスニペットからセクションに変更する必要があります。
sectionsディレクトリにbreadcrums.liquidを移動し、ファイルの末尾にschemaを追記しておきます。

{%- schema -%}
  {"name": "breadcrumbs"}
{% endschema %}

facets.jsを編集する

facets.jsの中身を読み解くのは、デザイナーにはなかなか骨が折れました。
当時(といってもまだ今年の話ですが)あまりにもわからなすぎて2ヶ月ほど保留にして、再度挑戦してうっすら理解できました。

renderPage()という何やらレンダリングに関係していそうな関数の中に、sections.forEach()というこれまたセクションに関係しそうな箇所があります。
その中で
「const url」とurlという変数を定義している箇所があります。ここをよくみると、セクションIDをパラメータにつけたURLを作ってrenderSectionFromFetch()に渡して処理しています。
renderSectionFromFetch()は何をしているかというと、渡されたURLから返ってきた値、つまりセクションのレンダリング内容を、さらにrenderProductGridContainer()などなどに渡しています。
renderProductGridContainer()などなどは、書き換えたいターゲットのHTML要素を、パースしたHTMLの中の同じターゲット要素の中身で置き換えています。

ややこしいですねー!

ま、ようするに上記の流れを逆向きに辿った結果、修正するところは3箇所です。
(元のコードに影響がないように、共通化せずにパンくず用の関数を増やしています)

renderPage()の中にurl_breadcrumbsの定義を追加

参考にした元のコードはファセットの条件を変更する際のイベントによってあれこれ処理を変えるためにあれこれ書かれています。
が、パンくずは単純に、パンくずのセクションIDパラメータ付きのURLでアクセスした時のレンダリング内容を読み込めばいいだけなので、ちょっとシンプルになります。

  static renderPage(searchParams, event, updateURLHash = true) {
    FacetFiltersForm.searchParamsPrev = searchParams;
    const sections = FacetFiltersForm.getSections();
    const countContainer = document.getElementById('ProductCount');
    const countContainerDesktop = document.getElementById('ProductCountDesktop');
    document.getElementById('ProductGridContainer').querySelector('.collection').classList.add('loading');
    if (countContainer){
      countContainer.classList.add('loading');
    }    
    if (countContainerDesktop){
      countContainerDesktop.classList.add('loading');
    }

    sections.forEach((section) => {
      const url_breadcrumbs = `${window.location.pathname}?section_id=breadcrumbs&${searchParams}`;
      FacetFiltersForm.renderBreadcrumbsFetch(url_breadcrumbs);          

      const url = `${window.location.pathname}?section_id=${section.section}&${searchParams}`;
        const filterDataUrl = element => element.url === url;
  
        FacetFiltersForm.filterData.some(filterDataUrl) ?
          FacetFiltersForm.renderSectionFromCache(filterDataUrl, event) :
          FacetFiltersForm.renderSectionFromFetch(url, event);          
    });

    if (updateURLHash) FacetFiltersForm.updateURLHash(searchParams);
  }

下記の2行を足しただけですね。

      const url_breadcrumbs = `${window.location.pathname}?section_id=breadcrumbs&${searchParams}`;
      FacetFiltersForm.renderBreadcrumbsFetch(url_breadcrumbs);          

これでAPIを叩くためのURLを作って、次に追加するパンくずのコードをフェッチするためのrenderBreadcrumbsFetch()に渡します。

renderBreadcrumbsFetch()を新しく追加

renderSectionFromFetch()を真似して、その下あたりにでも追加しておきます。

  static renderBreadcrumbsFetch(url_breadcrumbs) {
    fetch(url_breadcrumbs)
      .then(response => response.text())
      .then((responseText) => {
        const html_breadcrumbs = responseText;
        FacetFiltersForm.renderBreadcrumbs(html_breadcrumbs);
      });
  }

返ってきた値を、これまた次に追加するrenderBreadcrumbs()へ渡します。

renderBreadcrumbs()を新しく追加

renderProductGridContainer()を真似して、その下あたりにでも以下略。

  static renderBreadcrumbs(html_breadcrumbs) {
    document.getElementById('shopify-section-breadcrumbs').replaceWith(new DOMParser().parseFromString(html_breadcrumbs, 'text/html').getElementById('shopify-section-breadcrumbs'));
  }

ターゲットのセクションのinnerHTMLにパースしたHTMLを入れるとネストになってしまうため、セクションまるごと置換するためにreplaceWith()を使用します。

なお、getSections()にパンくずも追加してやればいけんじゃねーの?って思っていろいろ試したのですがうまくいきませんでした。
「?section_id=${section.section}」の部分でbreadcrumbsのセクション名が取得できなくてコケるのです。

まあなんかJSつよつよマンにお願いしたらいろいろいい感じにできるのだと思いますが、私にはこれが限界でした。

おわりに

このパンくず、単純にHTMLコードを出力しているだけなので、ユーザーの回遊時の利便性などUIとしての役割を重視して作ることを念頭に、SEO的なあれそれに関してはJSON-LDなどであれそれすると良いと思います。

全然関係ない話なのですが、よほど特殊な事情がない限りジャンルはゲームソフト1タイトルにひとつしかないかなとは思うので、管理は商品タイプかメタフィールドでよくね?と思いますが、絆が伝説を紡ぎだすRPG』とか『「正義」を貫き通すRPG』とかも登録しておきたいんだぜ!みたいなケースは、タグにしろメタフィールドにしろ、絞り込み一覧にそのジャンル表示されるのがしんどいのでどうするんでしょうね?
メタフィールドに絞り込み用ジャンル名と表示用ジャンル名と両方フィールド用意しておくのかな。大変だな。