ECの利益向上に繋がるShopifyカスタマイズ事例5
弊社ではShopifyでの制作をはじめ、様々なECサイトの制作のご依頼をいただいています。
特に、現在はECサイトの売上をUPしつつ効率的な運用を行いたい、月商を1,000万円以上の規模に成長させたい、月商億円規模のサイトを安全にシステム移管したい等、「利益を上げる知識」と「システム構築の知識」を総合的に持つことで解決できるご要望を、ワンストップで実現することを得意としています。
その現場の中で行ってきたカスタマイズ事例について、解説します。
事例5:メニュー構造を参照できるパンくずリスト
カスタマイズの背景
ECサイトにおけるパンくずリストは、ユーザビリティの向上はもちろん、サイト構造をクローラーに把握させるためのSEO(検索エンジン最適化)対策の側面も持っている重要なパーツです。しかしながらShopifyにはパンくずリストは標準搭載されておらず、また利用可能なテーマも増えて参りましたが、Shopifyのコレクションが階層構造を持たない関係上、パンくずリストも階層構造を持たないケースがほとんどである状態です。
月商規模が多くなるほど、影響は数字に現れます。弊社でも月商数億円規模のECサイト(国産ASPを利用)のShopifyへのリニューアルをご相談をいただいた際、その改善が必要だったため、実施した構築事例になります。
カスタマイズ前の課題
- Shopifyの利用予定テーマではパンくずリストが存在しなかった。
 
- リニューアル前のサイト同様のパンくずリスト構造を持つ必要がある。
 
- 商品数が膨大であるため、一度設置すると自動的に動く必要がある。
 
- アプリでの動作遅延の影響はできるだけ避けたい。
 
- 構造化マークアップの必要がある。
 
上記の課題から、アプリの機能に頼らず、テーマへのLiquid開発での課題解決に取り組みました。
カスタマイズでの要件・解決案
- パンくずリスト用のメニューを用意
 
- ページがメニューにある場合は、メニューの構造に従ってパンくずリストを生成
 
- 商品の場合は、登録されているコレクションがメニューに有る場合に起動
 
- アプリを利用せず、liquidコードで設置
 
- 構造化マークアップの実装
 
実装方法
具体的に行った実装方法は下記のような方法を利用しています。
※各コードは例であり、機構を組み込むサンプルのため、あくまで機構づくりの参考として記載しています。
全体Liquidコード
このコードを、breadcrumb.liquidとして用意し、テーマのsnippetsにアップして、{% render ‘breadcrumbs’ %}で呼び出すことで、パンくずリストの設置が可能となります。
{%- unless template == 'index' or template == 'cart' or template == 'list-collections' or template == '404' -%}
  {%- comment %} ▼1) 必要な変数の定義 {% endcomment %}
  {%- assign t = template | split: '.' | first -%}
  {%- assign menu = linklists.main-menu -%}
  {%- assign menu_found = false -%}
  {%- comment %} ▼2) 商品ページの場合、所属コレクションのhandle一覧を取得 {% endcomment %}
  {%- if t == 'product' -%}
	{%- assign product_collection_handles = product.collections | map: 'handle' -%}
  {%- endif -%}
  {%- comment %} ▼3) メニュー内をスキャンし、「active/child_active」または「商品コレクションが含まれている」リンクがあれば menu_found = true {% endcomment %}
  {%- for link in menu.links -%}
	{%- assign is_collection_match_link = false -%}
	{%- if t == 'product' and link.type == 'collection_link' and link.object.handle in product_collection_handles -%}
	  {%- assign is_collection_match_link = true -%}
	{%- endif -%}
	{%- if link.active or link.child_active or is_collection_match_link -%}
	  {%- assign menu_found = true -%}
	{%- endif -%}
	{%- for sub_link in link.links -%}
	  {%- assign is_collection_match_sub_link = false -%}
	  {%- if t == 'product' and sub_link.type == 'collection_link' and sub_link.object.handle in product_collection_handles -%}
		{%- assign is_collection_match_sub_link = true -%}
	  {%- endif -%}
	  {%- if sub_link.active or sub_link.child_active or is_collection_match_sub_link -%}
		{%- assign menu_found = true -%}
	  {%- endif -%}
	  {%- for sub_sub_link in sub_link.links -%}
		{%- assign is_collection_match_sub_sub_link = false -%}
		{%- if t == 'product' and sub_sub_link.type == 'collection_link' and sub_sub_link.object.handle in product_collection_handles -%}
		  {%- assign is_collection_match_sub_sub_link = true -%}
		{%- endif -%}
		{%- if sub_sub_link.active or is_collection_match_sub_sub_link -%}
		  {%- assign menu_found = true -%}
		{%- endif -%}
	  {%- endfor -%}
	{%- endfor -%}
  {%- endfor -%}
  <nav class="breadcrumbs" role="navigation" aria-label="{{ 'general.breadcrumb.label' | t }}">
	<ol class="breadcrumbs__list" role="list">
	  {%- comment %} ▼4) まずHOMEリンクを表示 {% endcomment %}
	  <li class="breadcrumbs__item" role="listitem">
		{{ 'general.breadcrumb.home' | t | link_to: routes.root_url, class: 'breadcrumbs__link' }}
	  </li>
	  {%- comment %} ▼5) メニューに該当コレクション等がある場合、メニュー階層を表示。なければ従来のShopifyロジックで表示 {% endcomment %}
	  {%- if menu_found -%}
		{%- for link in menu.links -%}
		  {%- assign is_collection_match_link = false -%}
		  {%- if t == 'product' and link.type == 'collection_link' and link.object.handle in product_collection_handles -%}
			{%- assign is_collection_match_link = true -%}
		  {%- endif -%}
		  {%- if link.active or link.child_active or is_collection_match_link -%}
			<li class="breadcrumbs__item" role="listitem">
			  <a href="{{ link.url }}" class="breadcrumbs__link">{{ link.title }}</a>
			</li>
			{%- for sub_link in link.links -%}
			  {%- assign is_collection_match_sub_link = false -%}
			  {%- if t == 'product' and sub_link.type == 'collection_link' and sub_link.object.handle in product_collection_handles -%}
				{%- assign is_collection_match_sub_link = true -%}
			  {%- endif -%}
			  {%- if sub_link.active or sub_link.child_active or is_collection_match_sub_link -%}
				<li class="breadcrumbs__item" role="listitem">
				  <a href="{{ sub_link.url }}" class="breadcrumbs__link">{{ sub_link.title }}</a>
				</li>
				{%- for sub_sub_link in sub_link.links -%}
				  {%- assign is_collection_match_sub_sub_link = false -%}
				  {%- if t == 'product' and sub_sub_link.type == 'collection_link' and sub_sub_link.object.handle in product_collection_handles -%}
					{%- assign is_collection_match_sub_sub_link = true -%}
				  {%- endif -%}
				  {%- if sub_sub_link.active or is_collection_match_sub_sub_link -%}
					<li class="breadcrumbs__item" role="listitem">
					  <a href="{{ sub_sub_link.url }}" class="breadcrumbs__link">{{ sub_sub_link.title }}</a>
					</li>
				  {%- endif -%}
				{%- endfor -%}
			  {%- endif -%}
			{%- endfor -%}
		  {%- endif -%}
		{%- endfor -%}
	  {%- else -%}
		{%- comment %} ▼6) メニューに定義がない場合はShopify既存ロジック {% endcomment %}
		{%- case t -%}
		  {%- when 'page' -%}
			<li class="breadcrumbs__item" role="listitem">
			  <span aria-current="page">{{ page.title }}</span>
			</li>
		  {%- when 'product' -%}
			{%- if collection and collection.url -%}
			  <li class="breadcrumbs__item" role="listitem">
				<a href="{{ collection.url }}" class="breadcrumbs__link">{{ collection.title }}</a>
			  </li>
			{%- endif -%}
			<li class="breadcrumbs__item" role="listitem">
			  <span aria-current="page">{{ product.title }}</span>
			</li>
		  {%- when 'collection' and collection.handle -%}
			{%- if current_tags -%}
			  <li class="breadcrumbs__item" role="listitem">
				<a href="{{ collection.url }}" class="breadcrumbs__link">{{ collection.title }}</a>
			  </li>
			  <li class="breadcrumbs__item" role="listitem">
				<span aria-current="page">{{ current_tags | join: ' + ' }}</span>
			  </li>
			{%- else -%}
			  <li class="breadcrumbs__item" role="listitem">
				<span aria-current="page">{{ collection.title }}</span>
			  </li>
			{%- endif -%}
		  {%- when 'blog' -%}
			{%- if current_tags -%}
			  <li class="breadcrumbs__item" role="listitem">
				<a href="{{ blog.url }}" class="breadcrumbs__link">{{ blog.title }}</a>
			  </li>
			  <li class="breadcrumbs__item" role="listitem">
				<span aria-current="page">{{ current_tags | join: ' + ' }}</span>
			  </li>
			{%- else -%}
			  <li class="breadcrumbs__item" role="listitem">
				<span aria-current="page">{{ blog.title }}</span>
			  </li>
			{%- endif -%}
		  {%- when 'article' -%}
			<li class="breadcrumbs__item" role="listitem">
			  <a href="{{ blog.url }}" class="breadcrumbs__link">{{ blog.title }}</a>
			</li>
			<li class="breadcrumbs__item" role="listitem">
			  <span aria-current="page">{{ article.title }}</span>
			</li>
		  {%- else -%}
			<li class="breadcrumbs__item" role="listitem">
			  <span aria-current="page">{{ page_title }}</span>
			</li>
		{%- endcase -%}
	  {%- endif -%}
	</ol>
  </nav>
  {%- comment %} ▼JSON-LD構造化データ(SEO用) -------------------------------- {% endcomment %}
  <script type="application/ld+json">
  {
	"@context": "http://schema.org",
	"@type": "BreadcrumbList",
	"name": "{{ 'general.breadcrumb.label' | t }}",
	"itemListElement": [
	  {
		"@type": "ListItem",
		"position": 1,
		"item": {
		  "@id": "{{ shop.url }}",
		  "name": "{{ 'general.breadcrumb.home' | t }}"
		}
	  }
	  {%- assign position = 2 -%}
	  {%- if menu_found -%}
		{%- comment %}
		  メニュー内の各階層リンクを順番にJSON-LD出力  
		  「active or child_active or コレクション一致」したリンクをパンくず要素に加える
		{% endcomment %}
		{%- for link in menu.links -%}
		  {%- assign is_collection_match_link = false -%}
		  {%- if t == 'product' and link.type == 'collection_link' and link.object.handle in product_collection_handles -%}
			{%- assign is_collection_match_link = true -%}
		  {%- endif -%}
		  {%- if link.active or link.child_active or is_collection_match_link -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ link.url }}",
				"name": "{{ link.title }}"
			  }
			}
			{%- assign position = position | plus: 1 -%}
			{%- for sub_link in link.links -%}
			  {%- assign is_collection_match_sub_link = false -%}
			  {%- if t == 'product' and sub_link.type == 'collection_link' and sub_link.object.handle in product_collection_handles -%}
				{%- assign is_collection_match_sub_link = true -%}
			  {%- endif -%}
			  {%- if sub_link.active or sub_link.child_active or is_collection_match_sub_link -%}
				,
				{
				  "@type": "ListItem",
				  "position": {{ position }},
				  "item": {
					"@id": "{{ sub_link.url }}",
					"name": "{{ sub_link.title }}"
				  }
				}
				{%- assign position = position | plus: 1 -%}
				{%- for sub_sub_link in sub_link.links -%}
				  {%- assign is_collection_match_sub_sub_link = false -%}
				  {%- if t == 'product' and sub_sub_link.type == 'collection_link' and sub_sub_link.object.handle in product_collection_handles -%}
					{%- assign is_collection_match_sub_sub_link = true -%}
				  {%- endif -%}
				  {%- if sub_sub_link.active or is_collection_match_sub_sub_link -%}
					,
					{
					  "@type": "ListItem",
					  "position": {{ position }},
					  "item": {
						"@id": "{{ sub_sub_link.url }}",
						"name": "{{ sub_sub_link.title }}"
					  }
					}
					{%- assign position = position | plus: 1 -%}
				  {%- endif -%}
				{%- endfor -%}
			  {%- endif -%}
			{%- endfor -%}
		  {%- endif -%}
		{%- endfor -%}
	  {%- else -%}
		{%- comment %} メニューに無い場合は従来のShopifyロジック {% endcomment %}
		{%- case t -%}
		  {%- when 'page' -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ page.url }}",
				"name": "{{ page.title }}"
			  }
			}
		  {%- when 'product' -%}
			{%- if collection and collection.url -%}
			  ,
			  {
				"@type": "ListItem",
				"position": {{ position }},
				"item": {
				  "@id": "{{ collection.url }}",
				  "name": "{{ collection.title }}"
				}
			  }
			  {%- assign position = position | plus: 1 -%}
			  ,
			  {
				"@type": "ListItem",
				"position": {{ position }},
				"item": {
				  "@id": "{{ product.url }}",
				  "name": "{{ product.title }}"
				}
			  }
			{%- else -%}
			  ,
			  {
				"@type": "ListItem",
				"position": {{ position }},
				"item": {
				  "@id": "{{ product.url }}",
				  "name": "{{ product.title }}"
				}
			  }
			{%- endif -%}
		  {%- when 'collection' and collection.handle -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ collection.url }}",
				"name": "{{ collection.title }}"
			  }
			}
			{%- if current_tags -%}
			  {%- assign position = position | plus: 1 -%}
			  {%- capture tag_url -%}{{ collection.url }}/{{ current_tags | join: "+" }}{%- endcapture -%}
			  ,
			  {
				"@type": "ListItem",
				"position": {{ position }},
				"item": {
				  "@id": "{{ tag_url }}",
				  "name": "{{ current_tags | join: " + " }}"
				}
			  }
			{%- endif -%}
		  {%- when 'blog' -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ blog.url }}",
				"name": "{{ blog.title }}"
			  }
			}
			{%- if current_tags -%}
			  {%- assign position = position | plus: 1 -%}
			  {%- capture tag_url -%}{{ blog.url }}/tagged/{{ current_tags | join: "+" }}{%- endcapture -%}
			  ,
			  {
				"@type": "ListItem",
				"position": {{ position }},
				"item": {
				  "@id": "{{ tag_url }}",
				  "name": "{{ current_tags | join: " + " }}"
				}
			  }
			{%- endif -%}
		  {%- when 'article' -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ blog.url }}",
				"name": "{{ blog.title }}"
			  }
			}
			{%- assign position = position | plus: 1 -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ article.url }}",
				"name": "{{ article.title }}"
			  }
			}
		  {%- else -%}
			,
			{
			  "@type": "ListItem",
			  "position": {{ position }},
			  "item": {
				"@id": "{{ request.path }}",
				"name": "{{ page_title }}"
			  }
			}
		{%- endcase -%}
	  {%- endif -%}
	]
  }
  </script>
{%- endunless -%}
 
コード解説
1) 必要な変数の定義
{%- assign t = template | split: '.' | first -%}
{%- assign menu = linklists.main-menu -%}
{%- assign menu_found = false -%}
main-menuのhandleを持つ、メニューを取得し、menu_foundでメニューの階層構造が利用できるかどうかを判定するためのフラグを用意します。
2) 商品ページの場合、所属コレクションを取得
{%- if t == 'product' -%}
  {%- assign product_collection_handles = product.collections | map: 'handle' -%}
{%- endif -%}
商品ページの場合、その商品が所属するコレクションのhandle一覧を配列(product_collection_handles)として持っておきます。
※商品自体をメニューに登録しない場合も、コレクションがメニューに登録されていればパンくずリストに反映できます。
3) メニューをスキャンしてフラグをON
{%- for link in menu.links -%}
  ...
  {%- if link.active or link.child_active or is_collection_match_link -%}
	{%- assign menu_found = true -%}
  {%- endif -%}
  ...
{%- endfor -%}
メニュー内(親リンク、子リンク、孫リンク)に、現在のページがある場合、また商品ページの場合は所属しているコレクションがメニューに有る場合、メニューのパンくずリストを起動(menu_found = true)します。
4) HOMEリンクの表示
<li class="breadcrumbs__item" role="listitem">
  {{ 'general.breadcrumb.home' | t | link_to: routes.root_url, class: 'breadcrumbs__link' }}
</li>
パンくずの最初の階層としてHOMEを表示します。
5) メニューに該当があるかで表示分岐
{%- if menu_found -%}
  {%- comment %}メニュー階層で表示{% endcomment %}
{%- else -%}
  {%- comment %}Shopify標準ロジック{% endcomment %}
{%- endif -%}
menu_found がtrueの場合は、メニュー上のリンク階層をパンくずリストとして表示します。
	falseの場合は、従来のShopifyロジックでページ種類別(商品・コレクション・ブログなど)にパンくずリストを表示します。
6) JSON-LDで構造化データを提供
<script type="application/ld+json">
  {
	"@context": "http://schema.org",
	"@type": "BreadcrumbList",
	"name": "{{ 'general.breadcrumb.label' | t }}",
	"itemListElement": [
	  ...
	]
  }
</script>
Googleなどの検索エンジンでリッチリザルト(パンくずリスト表示)を得るための構造化マークアップデータとして定義します。
推奨の追加調整
使用時は、theme.liquidやheader.liquidなどにrenderで組み込み、全ページ一括での設置がおすすめです。またサイト特有の構造がある場合は、条件に合わせてif文を追加して、クライアントニーズに適ったパンくずリストの設置が効率的に可能となります。
結果
メニュー構造を参照できるパンくずリストの設置で、何千商品とあるECサイトでも、階層構造のあるパンくずリストが効率的に設置できました。既存サイトからのリニューアルについても売上規模を落とさず、Shopifyでのオムニチャンネルや機能活用により、よりECサイトからの利益が向上したリニューアルを実現できています。