テクニック

Shopifyブログ記事一覧のカスタマイズ|カード表示と絞り込み

この記事の対象: Shopifyのテーマファイルを自分で編集する制作者
読了時間: 約12分

Shopifyの標準ブログテンプレートは、テーマによってはテキスト中心のシンプルなリスト表示になっている。アイキャッチを大きく見せるカード型に組み替えたい、タグで記事を絞り込めるようにしたい、という要望はクライアントワークでよく出る。この記事では Online Store 2.0 テーマのセクションファイルを書き替えて、ブログ一覧をカード型レイアウトに変更し、タグフィルターとページ送りを実装するまでの手順をまとめた。Liquid のコードは省略せず全文を載せているので、コピーしてそのまま使える。


前提:対象テーマと編集するファイル

前提:対象テーマと編集するファイル

Online Store 2.0 テーマであること

この手順は Online Store 2.0 のテーマ構造を前提にしている。Dawn・Craft・Sense など Shopify の無料テーマはすべてこの形式。有料テーマでも「Online Store 2.0 対応」と記載があれば同じ手順で進められる。

作業は Shopify 管理画面の「オンラインストア → テーマ → コードを編集」から行う。

テーマ選びの判断基準については別の記事で整理している。

blog テンプレートと article テンプレートの違い

ブログ機能には、一覧と個別の 2 種類のテンプレートがある。

テンプレート 表示される URL 例 役割
templates/blog.json /blogs/news 記事の一覧ページ
templates/article.json /blogs/news/記事ハンドル 記事の個別ページ
テンプレートとセクションファイルの関係

templates/blog.json

  • sections/main-blog.liquid(今回編集する)

templates/article.json

  • sections/main-article.liquid(今回は触らない)

JSON テンプレートは「どのセクションを使うか」の指定だけ。HTML を書くのはセクション側。

今回編集するのは一覧を担当するセクションファイルのみ。個別記事(article)は触らない。

Dawn テーマであれば sections/main-blog.liquid が該当する。別のテーマを使っている場合は templates/blog.json を開いて、sections の中に記載されたファイル名を確認する。

編集前にバックアップを取る

ファイルを書き替える前に、対象セクションの全文をテキストファイルにコピーしておく。テーマ自体を複製するのが確実で、管理画面の「テーマ → アクション → 複製」からワンクリックでできる。


ブログ一覧をカード型に書き替えるコード

ブログ一覧をカード型に書き替えるコード

sections/main-blog.liquid の既存コードを以下で丸ごと置き換える。ファイルは「HTML・Liquid → CSS → schema」の 3 パートで構成されている。

HTML・Liquid 部分

ファイルパス: sections/main-blog.liquid

{%- -%} は Liquid の出力から余計な空白を除去する書き方。{% %} に置き換えても動作には影響しない。

{%- paginate blog.articles by section.settings.per_page -%}
<div class="blog-custom">

  <!-- タグフィルター -->
  <div class="blog-tags">
    <a
      href="{{ blog.url }}"
      class="blog-tags__item{% unless current_tags %} is-active{% endunless %}"
    >すべて</a>
    {%- for tag in blog.all_tags -%}
      <a
        href="{{ blog.url }}/tagged/{{ tag | handleize }}"
        class="blog-tags__item{% if current_tags contains tag %} is-active{% endif %}"
      >{{ tag }}</a>
    {%- endfor -%}
  </div>

  <!-- カード一覧 -->
  <div class="blog-cards">
    {%- for article in blog.articles -%}
      <a href="{{ article.url }}" class="blog-card">
        <div class="blog-card__image">
          {%- if article.image -%}
            <img
              src="{{ article.image | image_url: width: 600 }}"
              alt="{{ article.image.alt | escape }}"
              width="600"
              height="400"
              loading="lazy"
            >
          {%- else -%}
            <div class="blog-card__noimage">No Image</div>
          {%- endif -%}
        </div>
        <div class="blog-card__body">
          {%- if article.tags.size > 0 -%}
            <span class="blog-card__tag">{{ article.tags.first }}</span>
          {%- endif -%}
          <h2 class="blog-card__title">{{ article.title }}</h2>
          <time
            class="blog-card__date"
            datetime="{{ article.published_at | date: '%Y-%m-%d' }}"
          >{{ article.published_at | date: '%Y.%m.%d' }}</time>
        </div>
      </a>
    {%- endfor -%}
  </div>

  <!-- ページネーション -->
  {%- if paginate.pages > 1 -%}
    <nav class="blog-pager" aria-label="ブログのページ送り">
      {%- if paginate.previous -%}
        <a href="{{ paginate.previous.url }}" class="blog-pager__link">← 前へ</a>
      {%- endif -%}
      {%- for part in paginate.parts -%}
        {%- if part.is_link -%}
          <a href="{{ part.url }}" class="blog-pager__link">{{ part.title }}</a>
        {%- else -%}
          <span class="blog-pager__link is-current" aria-current="page">{{ part.title }}</span>
        {%- endif -%}
      {%- endfor -%}
      {%- if paginate.next -%}
        <a href="{{ paginate.next.url }}" class="blog-pager__link">次へ →</a>
      {%- endif -%}
    </nav>
  {%- endif -%}

</div>
{%- endpaginate -%}

CSS 部分

同じファイルの Liquid コードの直後に、<style> タグで追加する。

<style>
.blog-custom {
  max-width: 1200px;
  margin: 0 auto;
  padding: 40px 20px;
}

/* タグフィルター */
.blog-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 32px;
}
.blog-tags__item {
  display: inline-block;
  padding: 6px 16px;
  border: 1px solid #ccc;
  border-radius: 20px;
  font-size: 14px;
  color: #333;
  text-decoration: none;
  transition: background .15s, color .15s;
}
.blog-tags__item:hover {
  background: #f5f5f5;
}
.blog-tags__item.is-active {
  background: #333;
  color: #fff;
  border-color: #333;
}

/* カード一覧 */
.blog-cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
@media (max-width: 768px) {
  .blog-cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (max-width: 480px) {
  .blog-cards {
    grid-template-columns: 1fr;
  }
}

/* カード本体 */
.blog-card {
  display: block;
  text-decoration: none;
  color: inherit;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .08);
  transition: box-shadow .2s;
}
.blog-card:hover {
  box-shadow: 0 4px 12px rgba(0, 0, 0, .15);
}
.blog-card__image img {
  width: 100%;
  height: auto;
  display: block;
  aspect-ratio: 3 / 2;
  object-fit: cover;
}
.blog-card__noimage {
  aspect-ratio: 3 / 2;
  background: #f0f0f0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #aaa;
  font-size: 14px;
}
.blog-card__body {
  padding: 16px;
}
.blog-card__tag {
  display: inline-block;
  font-size: 12px;
  color: #666;
  background: #f5f5f5;
  padding: 2px 10px;
  border-radius: 4px;
  margin-bottom: 8px;
}
.blog-card__title {
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  margin: 0 0 8px;
}
.blog-card__date {
  font-size: 13px;
  color: #999;
}

/* ページネーション */
.blog-pager {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-top: 48px;
}
.blog-pager__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-size: 14px;
  color: #333;
  text-decoration: none;
}
.blog-pager__link:hover {
  background: #f5f5f5;
}
.blog-pager__link.is-current {
  background: #333;
  color: #fff;
  border-color: #333;
  pointer-events: none;
}
</style>

schema 部分

CSS の下、ファイルの末尾に追加する。この {% schema %} を書いておくと、Shopify のテーマエディタ(管理画面の「カスタマイズ」)から 1 ページあたりの表示件数を変更できるようになる。

{% schema %}
{
  "name": "ブログ記事一覧",
  "tag": "section",
  "settings": [
    {
      "type": "range",
      "id": "per_page",
      "label": "1ページの表示件数",
      "min": 3,
      "max": 24,
      "step": 3,
      "default": 12
    }
  ]
}
{% endschema %}

sections/main-blog.liquid には、上から順に「Liquid(HTML)→ <style> → {% schema %}」の 3 ブロックがすべて入る。1 ファイルにまとまっている構成になる。


テンプレートの要点:画像・タグ・ページ送り

テンプレートの要点:画像・タグ・ページ送り

アイキャッチ未設定時の処理

article.image が未設定(nil)の記事では、{%- if article.image -%} の分岐により、画像の代わりにグレー背景の「No Image」が表示される。

共通のフォールバック画像を使いたい場合は、画像ファイルをテーマの assets/ フォルダにアップロードしたうえで、else ブロックを次のように書き替える。

{%- else -%}
  <img
    src="{{ 'blog-default.jpg' | asset_url }}"
    alt="記事のイメージ"
    width="600"
    height="400"
    loading="lazy"
  >
{%- endif -%}

ファイル名 blog-default.jpg は、実際にアップロードしたファイル名に合わせて変更する。

タグフィルターの仕組み

Shopify にはタグで記事を絞り込む機能が標準で備わっている。URL のパスに /tagged/タグ名 を付けると、そのタグが付いた記事だけが blog.articles に返される仕組みで、JavaScript は不要。

/blogs/news              → すべての記事
/blogs/news/tagged/tips  → 「tips」タグが付いた記事のみ

Liquid の blog.all_tags はそのブログで使われている全タグの配列を返す。current_tags はいまフィルター中のタグが入るので、選択状態のハイライトに使っている。

カードに表示するタグは article.tags.first(最初の 1 つだけ)にしている。すべてのタグを並べたい場合は、{%- for t in article.tags -%} でループすれば出力できる。ただしカード上にタグを並べすぎるとレイアウトが崩れやすいため、運用ルール側で「1 記事にメインタグ 1 つ」と決めるほうが管理しやすい。

タグの URL は検索エンジンにもインデックスされる。不要なタグページがインデックスに含まれるのを避けたい場合は、テーマの <head> 内で current_tags の有無を判定して noindex を出力する対応が必要になる。Shopify の URL 構造と SEO の関係は別の記事で扱っている。

paginate の動きと上限

{%- paginate blog.articles by section.settings.per_page -%} は、一覧を指定件数ずつに分割してページ送りを有効にするタグ。schema の default で初期値を 12 にしているため、設定を変えなければ 12 件ずつ表示される。この paginate で囲んだ範囲の中でのみ、ページ数や前後リンクなどの paginate オブジェクトが使える。

注意すべき点は 2 つある。

  • by の最大値は 50。それ以上を指定するとエラーになる。schema の range で max: 24 にしているのは、実用上それ以上だとページの読み込みが重くなるため
  • paginate の外で blog.articles をループすると、最大 50 件までしか取得できない。記事が 50 件を超えるブログでは、古い記事が一覧に出てこなくなる。paginate で囲むのを忘れないこと

変更してよい箇所の一覧

変更してよい箇所の一覧

コードをコピーした後、案件に合わせて調整するポイントを表にまとめた。

箇所 初期値 変更方法
1 ページの表示件数 12 件 テーマエディタのセクション設定で変更。または schema の default を書き替え
PC のカラム数 3 列 CSS の repeat(3, 1fr) の数字を変更
タブレットのカラム数 2 列 @media (max-width: 768px) 内の repeat(2, 1fr) を変更
スマホの切り替え幅 480px @media (max-width: 480px) の値を変更
カードの角丸 8px .blog-card の border-radius を変更。角丸なしなら 0
カードの影 0 1px 4px .blog-card の box-shadow を変更。影なしなら none
画像の比率 3:2 aspect-ratio: 3 / 2 を 16 / 9 や 1 / 1 に変更
アクティブタグの色 #333(黒) .blog-tags__item.is-active の background と border-color を変更
「No Image」の文言 No Image Liquid 内の .blog-card__noimage の中身を書き替え
コンテナの最大幅 1200px .blog-custom の max-width を変更

CSS の aspect-ratio プロパティはすべての主要ブラウザで対応済み(Chrome 88 以降・Safari 15 以降・Firefox 89 以降)。2022 年以降にリリースされたブラウザであれば問題ない。


うまくいかないとき

うまくいかないとき

カード一覧が表示されず白紙になる

Liquid の構文エラーが起きている可能性が高い。コードエディタでファイルを保存したとき、画面上部に赤いエラーメッセージが出ていないか確認する。{%- endfor -%} や {%- endif -%} の閉じ忘れが原因になりやすい。

タグフィルターのボタンが「すべて」しか表示されない

記事にタグが 1 つも付いていないと blog.all_tags は空になる。Shopify 管理画面の「ブログ記事」から各記事を編集してタグを追加する。タグは記事編集画面の右カラムにある「タグ」欄から入力できる。

ページ送りが出ない

公開済みの記事数が per_page(初期値 12)以下の場合、paginate.pages > 1 の条件を満たさないためページ送りは表示されない。テスト段階では表示件数を 3 に下げて確認するとよい。

アイキャッチ画像がぼやけて見える

image_url: width: 600 の 600 を大きくする。Retina ディスプレイ対応なら width: 1200 にすると改善する。ファイルサイズが増えるぶん読み込みは遅くなるため、loading="lazy" は外さないようにする。

保存時に「このテンプレートはセクション〇〇を使用しています」のエラーが出る

templates/blog.json 内で指定されているセクション名と、実際のファイル名が食い違っている。JSON の "type" が "main-blog" なら、ファイル名は sections/main-blog.liquid にする必要がある。

Shopify のテーマカスタマイズ全般で「どこまでできて、どこからが無理なのか」を事前に把握しておくと、作業の見通しが立てやすい。


よくある質問

よくある質問

ブログが複数ある場合、それぞれ別のレイアウトにできますか?

できます。templates/blog.json を複製して templates/blog.alternate.json を作り、中で別のセクションファイルを指定すれば、ブログごとに異なるテンプレートを割り当てられます。Shopify 管理画面の各ブログ設定からテンプレートを選択してください。

タグではなくカテゴリで分類することはできますか?

Shopify のブログには WordPress のような「カテゴリ」機能がありません。分類にはタグを使います。表示上カテゴリのように見せたい場合は、運用ルールで「1 記事にメインタグを 1 つ付ける」と決めて article.tags.first で取得するのが現実的です。

抜粋文をカードに表示するにはどうすればよいですか?

.blog-card__body 内の <h2> の下に <p>{{ article.excerpt_or_content | strip_html | truncate: 80 }}</p> を追加します。strip_html で HTML タグを除去し、truncate: 80 で 80 文字に切り詰めます。記事の「抜粋」欄に入力がなければ本文の先頭が使われます。

Dawn 以外のテーマでもこのコードは使えますか?

Online Store 2.0 テーマであれば、セクションファイルの書き方は共通です。ファイル名が異なる場合がありますが、templates/blog.json を開けばどのセクションが使われているかわかります。テーマ独自の CSS が干渉する場合は、クラス名にプレフィックスを付けて(例: my-blog-card)回避してください。

paginate の by に変数ではなく固定値を使ってもよいですか?

{%- paginate blog.articles by 12 -%} のように数値を直接書いても動作します。ただし schema と組み合わせて section.settings.per_page を使うほうが、テーマエディタから件数を変更できるため運用上は便利です。固定値にする場合は 50 を超えないよう注意してください。


まとめ

Shopify のブログ一覧をカード型に変えるには、セクションファイル 1 つの書き替えで対応できます。タグフィルターは Shopify が標準で持っている URL ベースの仕組みをそのまま使うため、JavaScript を追加する必要はありません。paginate は by の最大値が 50 である点と、囲み忘れると 50 件以上の記事が取得できない点だけ押さえておけば問題ありません。

コードをコピーしたら、カラム数・配色・画像比率などを案件の要件に合わせて調整してください。ブログ以外のページ構成にも興味があれば、以下の記事で会社概要ページの組み方を扱っています。