Shopify

Shopify : Créer un fil d’ariane parfait (sans app)

Nous travaillons de plusieurs mois sur notre thème Shopify et nous souhaitons partager notre approche et quelques tips sur Shopify (mais valable aussi pour d’autres plateformes ecommerce)

Notre approche consiste à créer des composants pour les boutiques en ligne en respectant cette checklist :

  • ROIste : est ce que le composant peut être optimisé pour faire gagner de l’argent à nos clients (par ex., proposer du crosselling à l’ajout au panier avec des produits en stock et pertinents, ou simplement le composant remplace une app payante)
  • Performance : est-ce que le composant ne ralentit pas le site ? le code est optimal ?
  • SEO ready : est ce que le composant respecte les guidelines SEO (nous avons une check-list complète)
  • Accessible : au sens strict du terme et selon les contrainte de la plateforme : en respectant le RGAA ou WCAG et les bonnes pratiques d’assurance qualité web (OPQUAST);
  • UX/UI : le composant doit être fonctionnel, esthétique et intuitif pour les utilisateurs finaux
  • Facilité d’utilisation pour le ecommerçant : pour le côté back, le ecommerçant doit être guidé pas à pas pour configurer facilement son composant
  • Facile à maintenir : les développeurs back ou front doivent pouvoir comprendre, modifier et faire évoluer le composant sans difficulté grâce à un code propre, bien documenté et respectant les standards de développement


Bien entendu, tous ces points sont liés. Un site accessible et performant favorisera le SEO et donc le ROI.


Un fil d’Ariane (breadcrumb) est un élément essentiel pour la navigation d’un site e-commerce, car il permet aux utilisateurs de voir où ils se trouvent et de naviguer facilement vers les pages parentes. Cependant, Shopify ne propose pas nativement cet élément de navigation, contrairement à d’autres CMS comme Prestashop, Magento, WP…

Ce tutoriel vous explique comment créer un breadcrumb personnalisé et dynamique sur Shopify, en utilisant liquid et les metafields.

Créer la logique du breadcrumb dans Shopify sans app

1. Ajouter les Metafields Requis

Un metafield est un champ personnalisable qui peut être ajouté par l’admin de la boutique afin de rajouter des champs supplémentaires à différents éléments.

Avant de commencer à coder, il est nécessaire d’ajouter deux metafields à votre boutique Shopify :

  • Un metafield « Collection par défaut » pour les produits
  • Un metafield « Collection parente » pour les collections.

Ajouter un Metafield « default_collection » sur les Produits

  1. Accédez à votre interface Shopify.
  2. Rendez-vous dans Paramètres > Données personnalisées.
  3. Cliquez sur Produits et ajoutez un nouveau metafield avec ces paramètres :
    • Nom : Default Collection
    • Namespace et key : custom.default_collection
    • Type : Référence de collection
    • Validation : Uniquement une seule collection
Shopify : Créer un fil d'ariane parfait (sans app) - 1

2. Ajouter un Metafield « parent_collection » sur les Collections

  1. Allez dans Paramètres > Données personnalisées.
  2. Choisissez Collections et créez un nouveau metafield :
    • Nom : Parent Collection
    • Namespace et key : custom.parent_collection
    • Type : Référence de collection
    • Validation : Uniquement une seule collection
Shopify : Créer un fil d'ariane parfait (sans app) - 2

Une fois ces metafields ajoutés, vous pourrez les utiliser dans votre code Liquid pour générer dynamiquement le fil d’Ariane. On passera donc via une section “breadcrumb.liquid” qui pourra être ensuite appelée dans vos pages.

3. Comprendre la Structure du Fil d’Ariane

Ce fil d’Ariane repose sur les collections parent-enfant d’un produit ou d’une collection. Il est construit dynamiquement en parcourant les collections assignées via les metafields Shopify.

Nous allons :

  • Récupérer la collection associée au produit ou à la collection actuelle.
  • Remonter la hiérarchie des collections via les metafields.
  • Empêcher les boucles infinies en vérifiant les duplications.
  • Ajouter un lien « Accueil » si un produit n’a pas de collection par défaut.
  • Afficher le fil d’Ariane avec un style personnalisable.

4. Gestion du fil d’ariane sur les pages collection

La construction du fil d’ariane commencera par la fin :

  • on récupère la collection courante
  • on vérifie si la collection courante a une collection parente
  • si la collection parente a un metafield parent_collection de renseigner, et ainsi de suite..

Nous allons pouvoir commencer d’écrire notre section “breadcrumb.liquid” qui contiendra la logique et le rendu de notre fil d’ariane.
Tout d’abord, le code vérifie si la page actuelle est une fiche produit ou une collection :

{% if template.name == 'product' or template.name == 'collection' %}
  {% assign breadcrumb_handles = '' | split: ',' %}
  {% if template.name == 'collection' %}
    {% assign current_collection_handle = collection.handle %}
  {% endif %}

Cela initialise un tableau breadcrumb_handles vide et assigne la collection du produit ou la collection actuelle.

5. Parcourir les Collections Parents

On parcourt jusqu’à 5 niveaux de profondeur pour trouver les collections parentes :

  {% assign max_depth = 5 %}
  {% for i in (1..max_depth) %}
    {% if current_collection_handle == blank %}
      {% break %}
    {% endif %}
    {% assign current_collection = collections[current_collection_handle] %}

Ici, max_depth limite le nombre de niveaux à 5 pour éviter les boucles infinies ainsi qu’un fil d’ariane « trop long », et on interrompt la boucle si current_collection_handle est vide.

6. Éviter les Boucles Infinies

Avant d’ajouter une collection à la liste, on vérifie à l’aide du nouveau filtre “| has” qu’elle n’est pas déjà présente pour éviter les boucles infinies :

{% assign is_duplicate = breadcrumb_list | has: current_collection_handle %}
{% if current_collection_handle == blank or is_duplicate != blank %}
 {% break %}
{% endif %}

C’est un scénario qui peut arriver si, par exemple, on définit la collection parente N-1 sur la collection N-2, qui à son tour , a comme collection parente, la collection N-2 :

Shopify : Créer un fil d'ariane parfait (sans app) - 3

7. Ajouter la collection parente

Si la collection actuelle a une collection parente définie dans son metafield, on met à jour current_collection_handle :

{% assign temp_array = current_collection_handle | split: ',' %}
        {% assign breadcrumb_handles = breadcrumb_handles | concat: temp_array %}
        {% assign parent_metafield = current_collection.metafields.custom.parent_collection %}
        {% if parent_metafield.value.handle != blank %}
          {% assign current_collection_handle = parent_metafield.value.handle %}
        {% else %}
          {% assign current_collection_handle = blank %}
        {% endif %}

8. Modification de la section pour les pages produit

Pour les pages produit, nous devons simplement construire notre arbre des collections à partir de la collection par défaut du produit (stockée dans le metafield default_collection) :

 {% comment %}get current collection handle{% endcomment %}
  {%- if template.name == 'collection' -%}
      {% assign current_collection_handle = collection.handle %}
    {% elsif template.name == 'product' %}
      {% assign current_collection_handle = product.metafields.custom.default_collection.value.handle %}
  {% endif %}

Afficher le fil d’ariane

1. Affichage simple

Commençons par affiche simple du fil d’ariane.
Notez bien le reversed sur le breadcrumb_list, pour reprendre dans l’ordre l’arborescence

 <nav>
    <ol>
      <li>
        <a href="{{ routes.root_url }}">Home</a>
      </li>
      {% for item_handle in breadcrumb_list reversed %}
        {% assign item = collections[item_handle] %}
        <li>
          <a href="{{ item.url }}">{{ item.title }}</a>
        </li>
      {% endfor %}
      {% if template.name == 'product' %}
        <li>{{ product.title }}</li>
      {%  endif %}
    </ol>
  </nav>

On obtient un premier affichage :

Shopify : Créer un fil d'ariane parfait (sans app) - 4

2. Mise en forme CSS avec BEMIT

Je ne vais pas m’attarder sur l’intérêt de l’approche BEMIT mais sachez juste que cette méthode permet une organisation structurée et une maintenabilité améliorée.

  <style>
    {% comment %} where(html) => specificity will be 0, easy to override {% endcomment %}

    :where(html){
      --breadcrumb-padding-x: 0;
      --breadcrumb-padding-y: 0;
      --breadcrumb-margin-bottom: 1rem;
      --breadcrumb-font-size: 0,75rem;
      --breadcrumb-item-padding-x: 0.5rem;

    }

    .c-breadcrumb{
      display: flex;
      flex-wrap: wrap;
      padding: var(--breadcrumb-padding-y) var(--breadcrumb-padding-x);
      margin-bottom: var(--breadcrumb-margin-bottom);
      font-size: var(--breadcrumb-font-size);
      list-style: none;
    }
    .c-breadcrumb a{
      text-decoration: none;
    }
    .c-breadcrumb__item+.c-breadcrumb__item {
      padding-left: var(--breadcrumb-item-padding-x);
    }
    .c-breadcrumb__item+.c-breadcrumb__item::before {
      float: left;
      padding-right: var(--breadcrumb-item-padding-x);
      content: var(--breadcrumb-divider, "/");
    }
    .page-width--breadcrumb{
      width: 100%;
    }
  </style>
  <nav>
    <ol class="c-breadcrumb">
      <li class="c-breadcrumb__item">
        <a href="{{ routes.root_url }}">Home</a>
      </li>
      {% for item_handle in breadcrumb_list reversed %}
        {% assign item = collections[item_handle] %}
        {% assign cssclass = 'c-breadcrumb__item' %}
        {% if template.name == 'collection' and forloop.last %}
          {% assign cssclass = 'c-breadcrumb__item c-breadcrumb__item--active' %}
        {% endif %}
        <li class="{{ cssclass }}">
          {% if template.name == 'collection' and forloop.last %}
            {{ collection.title }}
            {% else %}
            <a href="{{ item.url }}">{{ item.title }}</a>
          {%  endif %}
        </li>
      {% endfor %}
      {% if template.name == 'product' %}
        <li class="c-breadcrumb__item">{{ product.title }}</li>
      {%  endif %}
    </ol>
  </nav>
Shopify : Créer un fil d'ariane parfait (sans app) - 5

Pour aller plus loin, ajoutons un peu plus de contrainte en prenant en compte les écritures RTL (right to left).
Pour l’instant, l’affichage de notre fil d’ariane est cassé :

Shopify : Créer un fil d'ariane parfait (sans app) - 6

Pour corriger cela, les propriétés CSS logiques sont d’une grande aide.
Voici le code css corrigé avec l’utilisation de margin-block-end, padding-inline-end etc…

  <style>
    .c-breadcrumb{
      display: flex;
      flex-wrap: wrap;
      padding: var(--breadcrumb-padding-y) var(--breadcrumb-padding-x);
      margin-block-end: var(--breadcrumb-margin-bottom);
      font-size: var(--breadcrumb-font-size);
      list-style: none;
    }
   
    .c-breadcrumb__item+.c-breadcrumb__item {
      padding-inline-start: var(--breadcrumb-item-padding-x);
    }
    .c-breadcrumb__item+.c-breadcrumb__item::before {
      float: inline-start;
      padding-inline-end: var(--breadcrumb-item-padding-x);
      content: var(--breadcrumb-divider, "/");
    }
  </style>

Le nouveau rendu en RTL :

Shopify : Créer un fil d'ariane parfait (sans app) - 7

3. Affichage mobile

En mobile l’affichage est géré de façon à cacher visuellement ce qui « dépasse » de l’écran. Tout est linéaire et est scrollable.
Pour cela, veuillez appliquer à votre fil d’ariane ces déclarations de style qui permettent de scroller avec le toucher et de cacher les effets par défaut de chaque navigateur de façon à améliorer l’expérience utilisateur sur mobile.

.c-breadcrumb {
            --bgRGB: 255, 255, 255;
            --bg: rgb(var(--bgRGB));
            --bgTrans: rgba(var(--bgRGB), 0);
            --shadow: rgba(0, 0, 0, 0.2);

            display: flex;
            flex-wrap: nowrap;
            justify-items: center;
            padding: var(--breadcrumb-padding-y) var(--breadcrumb-padding-x);
            margin-block-end: var(--breadcrumb-margin-bottom);
            font-size: var(--breadcrumb-font-size);
            overflow-x: auto;
            overflow-y: hidden;
            line-height: 1;
            scroll-snap-type: x mandatory;
            -webkit-overflow-scrolling: touch;
            scrollbar-width: none; /* Firefox */
            -ms-overflow-style: none; /* IE and Edge */
            width: 100%;
            border-radius: 4px;
            background:
                /* Shadow Cover LEFT */
                    linear-gradient(
                            to right,
                            var(--bg) 30%,
                            var(--bgTrans)
                    ) left center,

                        /* Shadow Cover RIGHT */
                    linear-gradient(
                            to left,
                            var(--bg) 30%,
                            var(--bgTrans)
                    ) right center,

                        /* Shadow LEFT */
                    linear-gradient(
                            to right,
                            var(--shadow),
                            var(--bgTrans)
                    ) left center,

                        /* Shadow RIGHT */
                    linear-gradient(
                            to left,
                            var(--shadow),
                            var(--bgTrans)
                    ) right center;

            background-repeat: no-repeat;
            background-size: 40px 100%, 40px 100%, 20px 100%, 20px 100%;
            background-attachment: local, local, scroll, scroll;
}

.c-breadcrumb::-webkit-scrollbar {
            display: none; /* Chrome, Safari, Opera */
}

.c-breadcrumb__item {
            white-space: nowrap;
            scroll-snap-align: start;
            scroll-snap-stop: always;
}

Toujours dans le sens de l’expérience utilisateur visuelle (visuelle car les lecteurs d’écran n’auront pas le souci), nous ajouterons également un effet de dégradé pour indiquer que le contenu est scrollable et le cacher si l’inverse est vrai.

Voyez ici un exemple de scroll possible sur la droite sur l’image de gauche et quand le scroll est possible à gauche sur l’image de droite.

Optimisation SEO et performance

1. Données structurées de fil d’Ariane

Côté SEO, la recommandation habituelle est d’ajouter des données structurées de fil d’ariane.
Contrairement à pas mal d’app qui ajoutent ce type d’info en javascript, on souhaite ajouter ces informations dans le code source.
On a 2 options :

  • avec JSON-LD
  • avec des microdonnées

Je me suis amusé à compter les octets entre la solution avec JSON-LD et les microdonnées : JSON-LD l’emporte sur les microdonnées. (Ok, c’est de la sur-optimisation avec un impact insignifiant…).

<script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {
        "@type": "ListItem",
        "position": 1,
        "name": "Accueil",
        "item": {{ shop.url | append: routes.root_url | json }}
      }
      {% assign position = 2 %}
      {% for item_handle in breadcrumb_list reversed %}
      {% assign collection = collections[item_handle] %}
        ,{
          "@type": "ListItem",
          "position": {{ position }},
          "name": {{ collection.title | json }},
          "item": {{ shop.url | append: collection.url | json}}
        }
        {% assign position = position | plus: 1 %}
  {% endfor %}
      {% if template.name == 'product' %}
        ,{
          "@type": "ListItem",
          "position": {{ position }},
          "name": {{ product.title | json }},
          "item": {{ shop.url | append: product.url }}
        }
      {% endif %}
  ]
}
</script>

2. Tip SEO

Vous pouvez remplacer le mot « Accueil » par un mot clef comme par ex. « Boutique Ski ».

Accessibilité (RGAA et WCAG)

1. Attributs aria

Nous utilisons les attributs aria-label et aria-current pour aider les utilisateurs de technologies d’assistance à comprendre ce qu’est cette navigation et où se trouve la page actuelle dans la structure.

On ajoute aria-label= »Fil d’ariane » sur l’élément nav; attention à bien traduire ce terme car on voit souvent « Breadcrumb » sur des sites français par ex.

Pour le aria-current, on le place sur le dernier élément de la liste (sur le li); on n’ajoute pas de lien sur le dernier élément du fil d’ariane pour des raisons SEO.

2. Le séparateur

Le séparateur entre les éléments du fil d’ariane est géré en css avec le pseudo élément ::before, et cela peut poser des problèmes car certaines technologies d’assistance peuvent lire ce contenu.
On place le séparateur dans un span avec l’attribut aria-hidden à true afin d’être caché par les lecteurs d’écran.

<a href="{{ item.url }}"><span aria-hidden="true">&#47;</span>{{ item.title }}</a>

4. Correction comportement Safari sur les listes

Safari ne reconnaît pas les listes ordonnées ou non ordonnées comme des listes dans l’arbre d’accessibilité si elles ont une valeur de style de liste nulle, à moins que la liste ne soit imbriquée dans l’élément de navigation.

Dans notre cas, ce n’est pas un sujet car la liste est bien dans un élément nav.
Par sécurité, on peut améliorer cela grâce à un font size de 0 sur le pseudo élément ::marker et supprimer la règle list-style:none et ajouter cette règle :

 .c-breadcrumb ::marker{
    font-size:0;
 }

Conclusion

Ce tutoriel vous permet de créer un fil d’Ariane dynamique sur Shopify basé sur la hiérarchie des collections, via les metafields. Vous pouvez maintenant l’intégrer à votre boutique et personnaliser son affichage selon vos besoins, via du style ou des options supplémentaires !

Nous n'avons pas pu confirmer votre inscription.
Votre inscription est confirmée.

Recevoir des tips Shopify

Inscrivez-vous à notre newsletter pour suivre nos actualités.

Nous utilisons Brevo en tant que plateforme marketing. En soumettant ce formulaire, vous acceptez que les données personnelles que vous avez fournies soient transférées à Brevo pour être traitées conformément à la politique de confidentialité de Brevo.

Le code complet commenté

{% if template.name == 'product' or template.name == 'collection' %}
  {% comment %} Initialisation d'un tableau vide pour stocker les handles des collections dans le chemin du fil d'Ariane {% endcomment %}
  {% assign breadcrumb_handles = '' | split: ',' %}

  {% comment %} Détermination de la collection actuelle selon le type de page {% endcomment %}
  {% if template.name == 'product' %}
    {% comment %} Pour une page produit, on utilise la collection par défaut définie dans les metafields du produit {% endcomment %}
    {% assign current_collection_handle = product.metafields.custom.default_collection.value.handle %}
  {% elsif template.name == 'collection' %}
    {% comment %} Pour une page collection, on utilise le handle de la collection actuelle {% endcomment %}
    {% assign current_collection_handle = collection.handle %}
  {% endif %}

  {% comment %} Définition d'une profondeur maximum pour éviter les boucles infinies {% endcomment %}
  {% assign max_depth = 5 %}

  {% comment %} Boucle remontant l'arborescence des collections parentes {% endcomment %}
  {% for i in (1..max_depth) %}
    {% comment %} Sortie de la boucle si on atteint une collection sans parent (racine) {% endcomment %}
    {% if current_collection_handle == blank %}
      {% break %}
    {% endif %}

    {% comment %} Récupération de l'objet collection à partir du handle {% endcomment %}
    {% assign current_collection = collections[current_collection_handle] %}

    {% if current_collection %}
      {% comment %} Vérification pour éviter les boucles infinies : on vérifie si cette collection est déjà dans notre chemin {% endcomment %}
      {% assign is_duplicate = breadcrumb_handles | has: current_collection_handle %}

      {% unless is_duplicate %}
        {% comment %} Ajout du handle de la collection actuelle au tableau des handles {% endcomment %}
        {% assign temp_array = current_collection_handle | split: ',' %}
        {% assign breadcrumb_handles = breadcrumb_handles | concat: temp_array %}

        {% comment %} Récupération du handle de la collection parente depuis les metafields {% endcomment %}
        {% assign parent_metafield = current_collection.metafields.custom.parent_collection %}
        {% if parent_metafield.value.handle != blank %}
          {% comment %} Si une collection parente existe, on remonte d'un niveau dans l'arborescence {% endcomment %}
          {% assign current_collection_handle = parent_metafield.value.handle %}
        {% else %}
          {% comment %} Sinon, on a atteint la racine, on arrête la boucle {% endcomment %}
          {% assign current_collection_handle = blank %}
        {% endif %}
      {% else %}
        {% comment %} Si on détecte une boucle (collection déjà rencontrée), on arrête {% endcomment %}
        {% break %}
      {% endunless %}
    {% else %}
      {% comment %} Si la collection n'existe pas, on arrête la boucle {% endcomment %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% comment %} Affichage du fil d'Ariane dans l'interface {% endcomment %}
  <nav role="navigation" class="breadcrumb" aria-label="vous êtes ici :">
    <ol class="breadcrumb-{{ section.settings.separator_style }}">
        <li>
          <a class="breadcrumb__link" href="{{ routes.root_url }}">Accueil</a>
        </li>
      {% comment %} Parcours des collections dans l'ordre inverse (de la plus générale à la plus spécifique) {% endcomment %}
      {% for handle in breadcrumb_handles reversed %}
        {% assign collection = collections[handle] %}
        <li>
          <a class="breadcrumb__link" href="{{ collection.url }}">{{ collection.title }}</a>
        </li>
      {% endfor %}
      {% comment %} Si on est sur une page produit, on ajoute le titre du produit à la fin (non cliquable) {% endcomment %}
      {% if template.name == 'product' %}
        <li>
          <a class="breadcrumb__link" aria-current="page">{{ product.title }}</a>
        </li>
      {% endif %}
    </ol>
  </nav>
{% endif %}

Ressources :