vendredi 25 septembre 2026

À propos

Contact

Blocs Gutenberg

Un bloc de filtres de catalogue avec Interactivity API et bindings

Étude de cas d'un bloc de filtres produits combinant état serveur partagé, navigation via le router et sources de bindings personnalisées, sans réinventer les bases.

Par Clément Hadrot • 20 janvier 2026 • 5 min de lecture • Aucun commentaire
Un bloc de filtres de catalogue avec Interactivity API et bindings

Un site e-commerce de fournitures industrielles avait besoin d’un catalogue filtrable par catégorie, disponibilité et fourchette de prix, sur un volume de 1 200 références réparties en une centaine de pages de résultats. L’objectif : que le changement d’un filtre ou d’une page mette à jour la grille de produits sans rechargement complet, tout en gardant chaque URL de résultat indexable et partageable telle quelle. Ce cas combine trois briques de l’Interactivity API déjà traitées séparément ailleurs — état, router, bindings — dans une seule implémentation réelle.

Ce n’est pas un tutoriel pas à pas des bases de chacune de ces API : c’est le compte-rendu des choix d’architecture retenus pour les faire cohabiter proprement sur un cas de volume réel.

L’état des filtres, global et partagé

Les filtres actifs (catégorie sélectionnée, fourchette de prix, disponibilité) vivent dans l’état global du store, initialisé côté serveur à partir des paramètres de l’URL courante, pour que l’état affiché corresponde toujours exactement à ce que l’URL décrit — un principe qui garantit qu’un lien partagé reproduit fidèlement les mêmes filtres chez le destinataire.

<?php
$filtres_actifs = array(
    'categorie'     => sanitize_text_field( $_GET['categorie'] ?? '' ),
    'prixMax'       => absint( $_GET['prix_max'] ?? 0 ),
    'disponibilite' => isset( $_GET['en_stock'] ),
);

wp_interactivity_state( 'agence/catalogue-filtres', array(
    'filtresActifs' => $filtres_actifs,
) );
?>

Chaque case à cocher ou champ de filtre modifie cet état global via une action, puis déclenche une navigation via le router vers l’URL correspondante — l’état ne fait que refléter localement ce qui va de toute façon être confirmé par la nouvelle page chargée en arrière-plan.

Le router, ciblé sur la grille uniquement

Seule la grille de résultats est marquée comme région de router (data-wp-router-region="grille-resultats") ; le panneau de filtres reste hors de cette région, car il ne doit pas être remplacé à chaque navigation — seul son état visuel (case cochée ou non) doit se synchroniser, ce que gère l’état partagé du store plutôt qu’un remplacement de DOM par le router.

store( 'agence/catalogue-filtres', {
    actions: {
        *appliquerFiltre( evenement ) {
            const { name, checked, value } = evenement.target;
            state.filtresActifs[ name ] = evenement.target.type === 'checkbox' ? checked : value;

            const url = construireUrlAvecFiltres( state.filtresActifs );
            const { actions: actionsRouter } = yield import( '@wordpress/interactivity-router' );
            yield actionsRouter.navigate( url, { force: false } );
        },
    },
} );
L'essentiel à retenir : Les filtres actifs vivent dans l'état global du store, partagés par toute la page ; Le router recharge uniquement la grille de résultats, pas les filtres eux-mêmes ; Une source de binding personnalisée relie le compteur de résultats au contenu réel

Une source de binding pour le compteur de résultats

Le nombre de résultats affiché en haut de la grille (« 1 200 références, 340 correspondent à vos filtres ») ne devait pas être recalculé côté client à partir d’une liste potentiellement partielle, mais refléter fidèlement le total réel calculé côté serveur au moment du rendu de chaque page filtrée. Plutôt que de dupliquer ce calcul en JavaScript, une source de block binding personnalisée relie directement un bloc Paragraphe à cette donnée serveur, sans code front supplémentaire pour cette partie précise.

add_action( 'init', function () {
    register_block_bindings_source( 'agence/compteur-filtres', array(
        'label'              => __( 'Compteur de résultats filtrés', 'agence' ),
        'get_value_callback' => function ( $args, $block_instance ) {
            $requete = new WP_Query( construire_args_requete_filtres() );
            return sprintf(
                '%d référence(s) sur %d au total',
                $requete->found_posts,
                wp_count_posts( 'produit' )->publish
            );
        },
    ) );
} );

Ce bloc Paragraphe est ensuite lié à cette source via l’attribut metadata.bindings, généré automatiquement à l’insertion du bloc dans le gabarit du catalogue, sans jamais nécessiter de composant React ni de directive d’interactivité pour ce fragment précis — un bon rappel que toute donnée dynamique n’a pas besoin de passer par l’Interactivity API si elle ne change qu’au rendu serveur.

Ce qui a demandé le plus d’itérations

  • Synchroniser l’état des cases à cocher de filtre avec l’URL après un clic sur le bouton retour du navigateur, qui ne passe pas systématiquement par les actions du store si le router restaure une page depuis son cache interne — une écoute de l’événement de navigation du router a été nécessaire pour resynchroniser l’état visuel des filtres à ce moment précis.
  • Éviter que deux clics rapides sur des filtres différents ne déclenchent une course entre deux navigations concurrentes du router, résolu en annulant explicitement la navigation précédente avant d’en lancer une nouvelle.

Bilan de charge

Sur les pages de résultats les plus filtrées (moins de dix produits affichés), le temps de réponse reste dominé par la requête WP_Query côté serveur, pas par le JavaScript front — un rappel que l’Interactivity API optimise la fluidité perçue de la navigation, pas la performance des requêtes de base de données sous-jacentes, qui reste un chantier indépendant.

Le choix qui a le mieux vieilli sur ce projet : ne pas chercher à tout faire avec une seule des trois briques de l’Interactivity API. Le compteur de résultats n’avait besoin que d’un binding serveur simple, pas d’un store JavaScript dédié — le résoudre autrement aurait ajouté de la complexité sans bénéfice mesurable.

En résumé

Ce catalogue de 1 200 références combine délibérément trois briques distinctes de l’écosystème de blocs modernes, chacune appliquée là où elle apporte un bénéfice réel : l’état global du store pour les filtres partagés, le router restreint à la seule grille de résultats, et un binding serveur simple pour un compteur qui n’avait besoin d’aucune logique côté client. La leçon principale : ne pas systématiser une brique par réflexe, mais choisir celle qui correspond exactement à la nature de chaque donnée affichée.

Partager :

À propos de l'auteur

Clément Hadrot

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi