Table des matières :
- Ce que fait réellement
ps_facetedsearch(et pourquoi les “filtres combinés” sont souvent mal compris) - Pré-requis et garde-fous (performance, staging, sécurité)
- Configurer
ps_facetedsearch: modèles de facettes par catégorie et filtres combinés - Indexation, SQL et coût réel des facettes (ce que vous devez mesurer)
- URLs de facettes, crawl budget et canonical : éviter le désastre SEO
- Personnalisation front + débogage : thèmes, AJAX, cache et cas tordus
La recherche à facettes PrestaShop n’est pas un moteur de recherche : c’est un module de navigation « layered » qui restreint une liste de produits (catégorie, résultats de recherche, nouveautés, etc.) en appliquant des contraintes (prix, attributs, caractéristiques, disponibilité…). Sur PrestaShop 8.1.x et 9.x, cette logique est portée par ps_facetedsearch et repose majoritairement sur de l’indexation SQL + du filtrage côté serveur, avec un rafraîchissement AJAX de la liste de produits.
Un point utile pour cadrer : ps_facetedsearch vise surtout les pages de listing (catégories, parfois résultats de recherche selon thème), alors qu’un “vrai” moteur (OpenSearch/Elasticsearch/Meilisearch) vise la pertinence et la tolérance (typos, synonymes, scoring). C’est pour ça qu’on obtient souvent une excellente UX “filtrage”… tout en atteignant des limites dès qu’on veut faire du booléen avancé.
Pour situer la source et suivre les évolutions : le module est maintenu en open source par PrestaShop sur GitHub (utile pour lire les issues/PR et comprendre certains comportements) : ps_facetedsearch sur GitHub
Dans cet article, je pars du principe que vous êtes en PrestaShop 8.1.7+ ou 9.1/9.2 (Symfony 6.4 côté BO), avec PHP 8.2/8.3. Adaptez si vous êtes sur une 8.0 ou une 1.7 encore en production : les comportements (notamment URL et templates) divergent.
Ce que fait réellement ps_facetedsearch (et pourquoi les “filtres combinés” sont souvent mal compris)
ps_facetedsearch construit un jeu de facettes à partir des données de catalogue : groupes d’attributs (déclinaisons), caractéristiques (features), marque, catégories, prix, condition, disponibilité, poids… Ces facettes sont ensuite affichées sur les pages de listing (typiquement catégories) et, à chaque interaction, le module renvoie une nouvelle liste de produits + les facettes recalculées. C’est très efficace UX, mais côté back-end ça peut devenir un gros consommateur de CPU/IO dès que vous dépassez quelques dizaines de milliers de produits ou que vous avez beaucoup de combinaisons.
Le point clé : les filtres combinés ne veulent pas dire “OR global partout”. Dans la plupart des implémentations e-commerce (dont PrestaShop), la combinaison se fait ainsi :
- Entre facettes différentes (ex. Taille + Couleur + Marque) : combinaison en AND (intersection).
- À l’intérieur d’une même facette (ex. Couleur = Noir et Couleur = Bleu) : multi-sélection souvent en OR (union), puis intersectée avec les autres facettes.
Concrètement, si votre filtre combiné se comporte “bizarrement”, c’est presque toujours une confusion entre ces deux niveaux de logique. Un exemple : Taille=42 (AND) Couleur=Noir|Bleu (OR), c’est attendu. Par contre demander Marque=A OR Taille=42 n’est pas un cas géré par le cœur : ça requiert une modélisation de requêtes et un front spécifique (et souvent un moteur type OpenSearch/Elasticsearch).
Mini-scénario (utile pour débugger rapidement la logique) :
- Catalogue “Chaussures” :
- Produit P1 : Taille 42, Couleur Noir, Marque A
- Produit P2 : Taille 42, Couleur Bleu, Marque B
- Produit P3 : Taille 43, Couleur Noir, Marque A
Si l’utilisateur coche Taille 42 et Couleur Noir + Bleu, le résultat logique attendu est :
- Taille=42 (P1,P2)
- Couleur=Noir|Bleu (P1,P2,P3)
- Intersection = (P1,P2)
Donc P1 et P2. Si vous ne voyez que P1, vous avez souvent un souci de multi-sélection côté UI (ou un thème qui force un “single select”).
Deux implications pratiques (souvent oubliées) :
- Si vous avez beaucoup de valeurs dans une facette (ex. Couleur avec 80 valeurs “marketing”), vous créez un énorme espace combinatoire et vous dégradez la lisibilité + le coût de calcul.
- Si un besoin métier réclame du booléen inter-facettes (A OR B sur des champs différents), vous sortez de la philosophie “navigation à facettes” : mieux vaut l’assumer tôt dans le cadrage.
Pré-requis et garde-fous (performance, staging, sécurité)
Avant de “juste” cocher des cases dans le BO, posez un cadre : ps_facetedsearch indexe et peut générer des requêtes lourdes. Sur un catalogue conséquent, une réindexation peut durer plusieurs minutes (voire heures) et déclencher des verrous ou du load IO côté MariaDB. Faites-le sur staging d’abord (idéalement un clone base + médias), et planifiez la prod hors pic.
Un garde-fou simple, souvent efficace en contexte e-commerce (soldes, drops, pics ads) : définir une fenêtre d’intervention où vous acceptez une baisse de perf, et surveiller pendant l’indexation (CPU, I/O disque, QPS MariaDB). Même sans APM, un minimum de monitoring (charge + latence) évite de “casser” une journée de ventes.
Côté sauvegarde, le minimum syndical si vous avez accès SSH :
# Backup base (à adapter)
mysqldump --single-transaction --quick -uUSER -p DBNAME > backup.sql
Deux précisions utiles :
--single-transactionest crucial pour InnoDB : il évite de bloquer l’écriture pendant le dump (dans la plupart des cas).- Pour les grosses bases, prévoyez aussi un dump compressé (gain disque + transfert) :
mysqldump --single-transaction --quick -uUSER -p DBNAME | gzip > backup.sql.gz
Et si vous touchez à la structure ou aux index SQL pour optimiser, vous devez être en capacité de rollback.
Sécurité : ps_facetedsearch a déjà été au centre de vulnérabilités sérieuses. Si vous n’avez pas lu l’analyse, commencez par l’article interne « Vulnérabilité PrestaShop ps_facetedsearch : analyse RCE via unserialize() ».
Ce n’est pas un détail : le manuel PHP rappelle explicitement le risque de désérialisation.
“Do not pass untrusted user input to unserialize() regardless of the options value.” — PHP Manual,
unserialize()(PHP Manual: unserialize())
Moralité : mettez à jour le module, désactivez les overrides exotiques qui touchent aux paramètres de facettes, et évitez toute logique “custom” qui persiste des payloads utilisateur sérialisés.
Checklist sécurité “facettes” (rapide, mais concrète) :
- module
ps_facetedsearchet core PrestaShop à jour (au minimum sur les correctifs sécurité), - BO non exposé (IP allowlist / VPN / URL non triviale + 2FA si possible),
- aucun override qui manipule directement la construction de requêtes à partir de paramètres non validés,
- logs serveur consultés après déploiement (pics de 500/502 ou patterns anormaux sur les endpoints AJAX).
Configurer ps_facetedsearch : modèles de facettes par catégorie et filtres combinés
Le module se configure par modèles de filtres (templates) que vous assignez ensuite à une ou plusieurs catégories (et parfois à d’autres pages de listing selon votre thème). L’erreur fréquente est d’essayer de faire “un modèle global” pour tout le site : techniquement ça marche, mais vous allez exposer des facettes inutiles, multiplier les combinaisons et alourdir le recalcul.
Approche pragmatique : 1 modèle par “famille” de catégories. Exemple e-commerce classique :
- Catégories Chaussures : Prix, Taille (attribut), Couleur (attribut), Marque, Disponibilité.
- Catégories Accessoires : Prix, Marque, Matière (caractéristique), Couleur.
- Catégories Électronique : Prix, Marque, Stock, Caractéristiques clés (RAM, stockage…).
Ça réduit les facettes “bruit” et améliore le taux de clic sur filtres utiles.
Une règle simple (souvent validée en CRO) : viser 4 à 8 facettes maximum réellement utilisées sur une catégorie. Au-delà, vous noyez l’utilisateur et vous augmentez le coût de recalcul. Si vous avez “besoin” de 15 filtres, c’est souvent un signal que :
- vos catégories sont trop larges,
- ou votre catalogue manque de normalisation (valeurs incohérentes),
- ou vous essayez de compenser un manque de moteur de recherche.
Tableau d’aide (exemple “familles” + objectif SEO/UX) :
| Famille | Objectif principal | Facettes à privilégier | Facettes à éviter (souvent) |
|---|---|---|---|
| Chaussures | Trouver la bonne variante | Taille, Couleur, Prix, Disponibilité | Poids, Condition (si non pertinent) |
| Mode / accessoires | Style + budget | Couleur, Matière, Marque, Prix | Trop de micro-caractéristiques |
| High-tech | Compatibilité/tech specs | Marque, Stockage, RAM, État (neuf/reconditionné) | Couleur si purement cosmétique |
Côté BO, le flux de config “propre” est généralement :
1) Modules > Gestionnaire de modules > Faceted search (ps_facetedsearch) > Configurer
2) Créer un modèle de filtres, choisir les facettes (attributs/caractéristiques/stock/prix…)
3) Assigner ce modèle aux catégories visées
4) Lancer / planifier l’indexation (et la relancer après gros imports)
Astuce qui évite des tickets “le filtre ne sort rien” : assurez-vous que les valeurs que vous filtrez sont bien présentes sur les produits et publiées dans le bon contexte boutique/langue. Exemple courant : une caractéristique “Matière” existe, mais n’est remplie que sur 30% des produits → la facette devient inutile (et peut donner l’impression que le filtre “casse” des listes).
Pour les filtres combinés, validez le comportement attendu en testant trois scénarios :
1) multi-sélection dans une facette (Couleur Noir + Bleu),
2) combinaison de facettes (Couleur Noir + Taille 42),
3) interaction avec tri/pagination.
En PrestaShop, le module encode souvent l’état dans une query string (souvent q= ou équivalent selon thème/versions). Votre job côté intégration est de vérifier que : (a) le front n’empêche pas la multi-sélection (UI), (b) l’AJAX renvoie bien une URL stable, (c) le back n’écrase pas l’état lors d’un changement de tri.
Point “local” très concret (observé sur des boutiques FR/BE/CH en multi-devise) : quand les prix sont affichés TTC avec règles de taxes différentes selon pays, le filtre prix peut donner des impressions d’incohérence si le thème mélange prix affiché et bornes calculées dans un autre contexte (pays/devise). En environnement international, testez le filtre prix au minimum sur :
- 2 pays,
- 2 devises,
- 2 groupes clients (B2C/B2B si vous en avez).
Enfin, en multiboutique, ne supposez pas que l’index et les templates sont identiques. Même avec un catalogue partagé, les règles de stock, de prix spécifiques et la visibilité des attributs peuvent diverger. Vérifiez votre contexte (id_shop, groupes clients, devises) avant d’accuser “le module” : beaucoup de “produits manquants” sont en réalité des produits hors groupe client / hors shop / hors visibilité.
Indexation, SQL et coût réel des facettes (ce que vous devez mesurer)
Sous le capot, ps_facetedsearch s’appuie sur des tables d’index (souvent héritées historiquement du “layered”) pour éviter de recalculer en full-scan à chaque clic. Selon votre version et votre historique d’upgrade, vous verrez typiquement des tables du style PREFIX_layered_* et/ou des tables dédiées au module. Ne restez pas au niveau “ça marche” : inspectez la volumétrie et les index.
Checklist minimale côté base :
- Taille des tables d’index (un catalogue avec beaucoup de déclinaisons peut faire exploser les lignes indexées).
- Index présents sur colonnes de jointure (
id_product,id_attribute,id_feature_value,id_shop, etc.). - Plans d’exécution sur les requêtes critiques (EXPLAIN) lors d’un filtrage “pire cas” (multi-sélection + prix + stock).
Deux commandes simples (souvent suffisantes pour “voir venir” un problème) :
-- Taille et volumétrie des tables d'index (adapter le préfixe)
SHOW TABLE STATUS LIKE 'ps_layered%';
SHOW TABLE STATUS LIKE 'ps_facetedsearch%';
-- Vérifier les index d'une table critique (exemple)
SHOW INDEX FROM ps_layered_product_attribute;
Côté exploitation (surtout si vous avez des imports fréquents), pensez “process” :
- Après import massif (ERP/PIM), qui déclenche la réindexation ? humain, cron, CI/CD ?
- Quel est le temps cible acceptable de réindexation en prod ?
- Quel est votre signal d’alerte : hausse du TTFB, erreurs 500, saturation I/O ?
Si vous n’avez pas de méthodo pour traiter les requêtes lentes, l’article interne « Audit MySQL/MariaDB : optimiser les requêtes lentes e‑commerce » est un bon cadre opérationnel.
Sur MariaDB/MySQL, deux points reviennent : (1) stats d’optimiseur obsolètes, (2) buffer pool sous-dimensionné. Un symptôme typique : le filtrage facetté déclenche des reads aléatoires et vous voyez le TTFB monter en flèche. Avant de “réécrire le module”, mesurez (slow log, performance_schema, APM) et corrigez la base (index, stats, config InnoDB) et le cache applicatif.
À noter (utile pour “GEO flavour” côté hébergement) : beaucoup de boutiques FR sont hébergées sur des VM mutualisées ou des offres où l’I/O disque est le vrai goulot (plus que le CPU). Les facettes étant très dépendantes des accès DB, un upgrade “stockage” (NVMe, IOPS garanties) donne parfois plus de gain qu’un refacto applicatif.
URLs de facettes, crawl budget et canonical : éviter le désastre SEO
Les facettes créent un problème structurel : une explosion combinatoire d’URLs (paramètres, pages, tris) pour un contenu très proche. À l’échelle SEO, c’est du bruit qui consomme crawl budget, dilue les signaux et peut générer des pages quasi-dupliquées. Si vous n’avez pas déjà cadré ce sujet, l’article interne « Budget de crawl PrestaShop : maîtriser URLs de filtres et facettes » pose les bases.
Côté technique, vous avez trois leviers qui se complètent (et qui doivent être cohérents) :
1) Canonical sur la catégorie “mère” (ou sur un sous-ensemble d’URLs facettées que vous assumez indexables),
2) Noindex sur les URLs de filtrage que vous ne voulez pas en SERP,
3) Contrôle de crawl (robots.txt / règles serveur) si vous devez empêcher des patterns massifs.
Attention : le canonical n’est pas une garantie. Google est explicite :
“Google considers the rel=canonical link element as a hint, not a directive.” — Google Search Central (Google Search Central: Consolidate duplicate URLs)
Donc si vos pages facettées sont massivement crawlées, “mettre un canonical” sans régler l’explosion d’URLs est rarement suffisant.
Décision SEO (simple, mais efficace) : séparer clairement les facettes en 2 classes.
- Facettes “SEO candidates” : celles qui peuvent créer une page utile (ex. Marque sur une catégorie forte, ou Taille sur une catégorie très demandée si vous avez un vrai contenu + une intention claire).
- Facettes “UX only” : celles qui doivent rester au service de l’utilisateur mais ne pas devenir une stratégie d’indexation (ex. “Disponible”, “Promotion”, combinaisons multiples de couleurs, tris, pages 2+).
En PrestaShop, branchez votre stratégie SEO sur ce que vous faites déjà sur catégories/produits : canonical, pagination, hreflang, etc. L’article interne « Référencement PrestaShop : optimiser fiches produits, catégories, canonical et hreflang » est directement pertinent.
Dans les faits, pour une boutique transactionnelle, l’indexation des pages facettées doit être très sélective (ex. seulement les facettes “Marque” sur certaines catégories stratégiques, avec URLs propres), sinon vous vous fabriquez vous-même un index de pages faibles.
Point pratique : si votre thème génère une URL facettée de type ?q=..., vous pouvez souvent :
- garder ces URLs noindex,
- et créer, pour les quelques cas SEO, des pages “propres” (catégories dédiées, landing pages, ou réécriture maîtrisée) avec contenu éditorial + maillage interne.
Personnalisation front + débogage : thèmes, AJAX, cache et cas tordus
Côté front, ps_facetedsearch s’appuie sur des templates (souvent Smarty) et un flux AJAX qui remplace à la fois le bloc facettes et le listing produits. Pour customiser proprement : ne modifiez pas le module en dur. Surchargez les templates dans votre thème (classiquement themes/<theme>/modules/ps_facetedsearch/...). Ça vous évite de perdre vos changements au prochain update et vous permet de versionner le thème.
Les personnalisations typiques qui valent le coup (et qui cassent souvent la logique si mal faites) :
- affichage du nombre de produits par valeur (counts),
- réordonnancement des valeurs (ex. tailles numériques),
- conversion d’une facette en “chips” horizontales sur mobile,
- gestion UX de la multi-sélection (contrôles clairs, bouton reset, état persistant).
Le piège : un thème peut intercepter les événements JS (ou redéfinir le refresh du listing) et casser la propagation de l’état de filtre. Avec PrestaShop 9 et des thèmes plus modernes, vous verrez parfois une couche supplémentaire (components, écouteurs d’événements, etc.). Déboguez avec DevTools : inspectez la requête AJAX, comparez l’URL renvoyée, vérifiez que la réponse contient bien products + rendered facets.
Procédure de debug “terrain” (rapide, et très efficace sur les cas tordus) :
1) Ouvrez la catégorie, puis Network (filtre XHR/fetch).
2) Cochez une facette → repérez l’appel AJAX ps_facetedsearch.
3) Vérifiez :
- le statut HTTP (200 vs 500/403),
- la taille de réponse (très petite = erreur ou cache/proxy qui renvoie autre chose),
- l’URL incluse/retournée (elle doit refléter l’état courant des filtres),
- l’absence d’erreurs JS (Console).
4) Répétez avec tri + pagination : c’est là que les thèmes “écrasent” souvent
q=.
La performance perçue dépend ensuite de votre stratégie de cache. Le filtrage facetté est, par définition, très peu “cacheable” en full-page cache si vous laissez l’infinité de combinaisons. Il faut donc jouer sur : cache objet (Redis), OPcache, cache SQL, et éventuellement micro-cache côté reverse proxy. L’article interne « Cache PrestaShop : stratégie 2026 OPcache, Redis, Varnish et CDN » donne une approche réaliste.
“There are only two hard things in Computer Science: cache invalidation and naming things.” — Phil Karlton (citation largement référencée)
En pratique, si vos facettes affichent des résultats incohérents, cherchez d’abord : invalidation de cache, index non reconstruit après import, règles de stock (out-of-stock), contraintes de groupes clients, et différences shop. Pour les shops qui “deviennent lents” spécifiquement sur catégories filtrées, corrélez avec TTFB et charge serveur ; l’article interne « PrestaShop lent : diagnostic TTFB, bots et optimisation serveur » sert de check-list.
Enfin, si vous avez besoin de filtres combinés plus avancés (logique booléenne arbitraire, facettes sur champs custom, scoring, tolérance aux fautes) : ps_facetedsearch atteindra ses limites. C’est le moment d’envisager une architecture de recherche dédiée (Meilisearch/OpenSearch) et une couche de facettes custom, plutôt que d’empiler des overrides fragiles sur un module conçu pour un modèle SQL “classique”.




