À qui s'adresse ce thème
Ce thème convient aux boutiques qui vendent des produits physiques avec un catalogue lisible : quelques dizaines à quelques milliers de références, des variantes, et un besoin de conversion plutôt que de vitrine.
Il est moins adapté aux catalogues de plusieurs dizaines de milliers de références, aux places de marché multi-vendeurs et aux tarifs négociés par client. Dans ces cas, nous partons sur un développement spécifique et nous le disons dès le premier échange.
[
{
"name": "Catalogue",
"settings": [
{
"type": "range",
"id": "produits_par_page",
"label": "Produits par page",
"min": 12, "max": 48, "step": 4,
"default": 24
},
{
"type": "checkbox",
"id": "filtres_actifs",
"label": "Filtres de collection",
"default": true
},
{
"type": "checkbox",
"id": "recherche_predictive",
"label": "Suggestions de recherche",
"default": true
}
]
}
]
Fiche produit
Le changement de variante ne recharge pas la page : le thème écoute l'événement, met à jour le prix, l'image et le stock, puis réécrit l'URL pour que le lien reste partageable.
Les combinaisons indisponibles sont marquées plutôt que retirées, pour que le visiteur comprenne ce qui existe.
{%- for option in product.options_with_values -%}
<fieldset class="variant" data-option="{{ option.position }}">
<legend>{{ option.name }}</legend>
{%- for value in option.values -%}
{%- assign dispo = value.variant.available -%}
<input
type="radio"
name="option-{{ option.position }}"
value="{{ value | escape }}"
{% if value.selected %}checked{% endif %}
{% unless dispo %}data-epuise="true"{% endunless %}>
{%- endfor -%}
</fieldset>
{%- endfor -%}
Collections et recherche
Les filtres se déclarent dans Shopify, pas en dur dans le thème : vous en ajoutez ou en retirez sans toucher au code. Le thème parcourt ce que la boutique expose et construit les cases à cocher avec le décompte de produits.
L'état des filtres reste dans l'URL, donc une sélection se met en favori ou se partage telle quelle.
{%- for filtre in collection.filters -%}
{%- case filtre.type -%}
{%- when 'list' -%}
<details class="filtre" open>
<summary>{{ filtre.label }}</summary>
{%- for valeur in filtre.values -%}
<label {% if valeur.count == 0 %}data-vide="true"{% endif %}>
<input type="checkbox"
name="{{ valeur.param_name }}"
value="{{ valeur.value }}"
{% if valeur.active %}checked{% endif %}>
{{ valeur.label }} <span>({{ valeur.count }})</span>
</label>
{%- endfor -%}
</details>
{%- when 'price_range' -%}
{%- render 'filtre-prix', filtre: filtre -%}
{%- endcase -%}
{%- endfor -%}
Panier et commande
Le panier s'ouvre en tiroir sans quitter la page en cours. L'ajout se fait par l'API du panier, la réponse met à jour le compteur et le sous-total, et le visiteur continue à parcourir le catalogue.
La barre de livraison offerte se calcule sur le total réel, en centimes, pour éviter les arrondis qui faussent le seuil.
async function ajouterAuPanier(variantId, quantite = 1) {
const reponse = await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [{ id: variantId, quantity: quantite }]
})
});
if (!reponse.ok) {
const erreur = await reponse.json();
return afficherErreur(erreur.description);
}
const panier = await (await fetch('/cart.js')).json();
majCompteur(panier.item_count);
majFranchisePort(panier.total_price);
ouvrirTiroir();
}
Personnalisation
Chaque section expose ses réglages dans un schéma JSON. C'est ce fichier qui fait apparaître les champs dans l'éditeur Shopify : vous modifiez le contenu et la mise en forme sans ouvrir un seul fichier.
Ajouter un réglage revient à ajouter une entrée dans ce schéma.
{% schema %}
{
"name": "Bannière",
"settings": [
{ "type": "image_picker", "id": "image", "label": "Image de fond" },
{ "type": "text", "id": "titre", "label": "Titre" },
{ "type": "richtext", "id": "texte", "label": "Texte" },
{
"type": "select",
"id": "hauteur",
"label": "Hauteur",
"options": [
{ "value": "petite", "label": "Petite" },
{ "value": "moyenne", "label": "Moyenne" },
{ "value": "plein", "label": "Plein écran" }
],
"default": "moyenne"
},
{ "type": "color_scheme", "id": "palette", "label": "Palette" }
],
"presets": [{ "name": "Bannière" }]
}
{% endschema %}
Performance
Chaque image est déclarée avec ses dimensions et une liste de largeurs. Le navigateur choisit le fichier adapté à l'écran au lieu de télécharger la version la plus grande.
L'image principale de la fiche est chargée en priorité ; tout le reste attend d'approcher de l'écran.
{%- assign largeurs = '400,600,800,1200,1600' -%}
<img
src="{{ image | image_url: width: 800 }}"
srcset="{%- for l in largeurs -%}
{{ image | image_url: width: l }} {{ l }}w
{%- unless forloop.last %}, {% endunless -%}
{%- endfor -%}"
sizes="(min-width: 990px) 50vw, 100vw"
width="{{ image.width }}"
height="{{ image.height }}"
alt="{{ image.alt | escape }}"
{% if prioritaire %}fetchpriority="high"{% else %}loading="lazy"{% endif %}>
Référencement
Les données structurées sont générées à partir du produit réel, pas saisies à la main : prix, devise, disponibilité et note moyenne suivent ce que contient la boutique.
C'est ce balisage qui permet à Google d'afficher le prix et les étoiles sous votre lien.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": {{ product.title | json }},
"image": {{ product.featured_image | image_url: width: 1200 | json }},
"description": {{ product.description | strip_html | truncate: 300 | json }},
"sku": {{ product.selected_or_first_available_variant.sku | json }},
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"offers": {
"@type": "Offer",
"price": {{ product.price | divided_by: 100.0 | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}",
"url": {{ request.origin | append: product.url | json }}
}
}
</script>
Installation et mise en ligne
Le thème est installé en publication différée : votre boutique actuelle continue de tourner pendant que nous travaillons sur la nouvelle version. Vous la voyez avancer par un lien d'aperçu.
À la mise en ligne, les anciennes adresses sont redirigées une à une pour ne perdre aucune position acquise.
Redirect from,Redirect to
/collections/sacs-cuir,/collections/maroquinerie
/produits/sac-porte-epaule,/products/sac-porte-epaule
/pages/a-propos,/pages/notre-histoire
/blog/actualites,/blogs/journal
/collections/all?sort=new,/collections/nouveautes
Limites à connaître
Le tunnel de paiement appartient à Shopify et n'est pas modifiable hors offre Plus. Les applications tierces sont le premier facteur de ralentissement d'une boutique : chaque ajout se pèse.
Les tarifs différenciés par client relèvent des fonctions B2B, disponibles selon votre offre Shopify. Un catalogue de plusieurs dizaines de milliers de références demande un moteur de recherche dédié, en complément du thème.
{%- comment -%}
Les applications s'injectent ici. Chacune ajoute ses propres
requêtes : on mesure avant et après, et on retire ce qui coûte
plus qu'il ne rapporte.
{%- endcomment -%}
{{ content_for_header }}
{%- if settings.mesure_active -%}
{%- render 'mesure-performance' -%}
{%- endif -%}
Discutons de votre boutique
Nous installons le thème, le paramétrons à votre catalogue et vous montrons comment le faire vivre. Dites-nous où vous en êtes.