# Migrer un catalogue WooCommerce vers WPGraphQL pour un frontend headless

> Un client voulait un frontend Next.js rapide sans abandonner WooCommerce en back-office. Retour sur une migration de catalogue vers WPGraphQL, ses lenteurs et ses contournements.

- Auteur : Clément Hadrot
- Publié le : 2025-08-06
- Mis à jour le : 2025-08-06
- Catégorie : E-commerce
- URL : https://wpmoderne.dev.wordpress-developpement.fr/ecommerce/migrer-catalogue-woocommerce-wpgraphql-headless/

## L’essentiel

- Le schéma WPGraphQL WooCommerce ne couvre pas tout par défaut
- Les variations de produit posent le vrai problème de performance
- Le cache de requête devient obligatoire, pas optionnel

Le client, une marque de vêtements techniques vendant en direct, voulait un site vitrine et un catalogue ultra-rapides, construits en Next.js, tout en gardant WooCommerce comme back-office de gestion des stocks et des commandes que son équipe connaissait déjà. La promesse du headless est séduisante sur le papier : découpler le rendu du back-office de gestion. Dans la pratique, la migration du catalogue vers WPGraphQL a pris quatre mois avant d'atteindre une stabilité satisfaisante, et plusieurs décisions prises en cours de route méritent d'être partagées.

Ce retour d'expérience ne couvre volontairement pas la Store API, déjà traitée dans un autre article : ici, il s'agit spécifiquement de l'extension WPGraphQL for WooCommerce, qui expose le catalogue et les commandes via un schéma GraphQL plutôt que via l'API REST classique.

## Pourquoi GraphQL plutôt que la Store API

Le choix s'est fait sur un critère précis : le frontend Next.js avait besoin de composer, en une seule requête, des données venant de plusieurs sources — produit, variations, avis, articles de blog liés via une taxonomie personnalisée. La Store API, pensée pour le panier et le checkout, ne couvre pas ce besoin de composition. WPGraphQL, avec son schéma unifié incluant WordPress et WooCommerce, permettait de récupérer exactement les champs nécessaires en un aller-retour réseau, réduisant nettement le nombre de requêtes par rapport à un enchaînement d'appels REST successifs.

## Le schéma par défaut ne suffit pas

La première désillusion est venue du schéma standard de l'extension : certains champs utiles pour l'affichage (attributs personnalisés de produit stockés en métadonnées, données de stock par entrepôt dans une configuration multi-dépôts) n'existent pas nativement dans le schéma exposé. Il a fallu étendre le schéma via `register_graphql_field()`, en s'appuyant sur les hooks fournis par WPGraphQL pour ajouter des champs personnalisés aux types `Product` et `ProductVariation`.

```
add_action( 'graphql_register_types', function() {
    register_graphql_field( 'Product', 'entrepotStock', array(
        'type' => 'Int',
        'description' => 'Stock disponible dans l\'entrepôt principal',
        'resolve' => function( $product ) {
            $product_id = $product->ID;
            return (int) get_post_meta( $product_id, '_stock_entrepot_principal', true );
        },
    ) );
} );
```

## Le vrai goulot d'étranglement : les variations

Pour un catalogue de vêtements techniques, chaque produit expose des variations par taille et par couleur, parfois une douzaine par produit. Une requête GraphQL naïve qui demande la liste des produits avec leurs variations imbriquées se transforme rapidement en un problème classique de requêtes en cascade (N+1) : chaque produit déclenche une requête séparée pour récupérer ses variations, ce qui, sur une page de catégorie affichant quarante produits, générait plusieurs centaines de requêtes SQL sous-jacentes.

> L'essentiel à retenir : Le schéma WPGraphQL WooCommerce ne couvre pas tout par défaut ; Les variations de produit posent le vrai problème de performance ; Le cache de requête devient obligatoire, pas optionnel

La solution est venue du chargement différé par lots (dataloader), un pattern que WPGraphQL implémente nativement pour la plupart de ses résolveurs, à condition de l'utiliser correctement dans les champs personnalisés. Un résolveur mal écrit, qui appelle directement `wc_get_product()` à l'intérieur de la boucle de résolution plutôt que de s'appuyer sur le chargeur par lots fourni par WPGraphQL, réintroduit le problème même quand l'extension elle-même est bien conçue.

## Le cache, obligatoire et pas optionnel

Sur un catalogue de cette taille, servir chaque requête GraphQL sans cache s'est révélé intenable dès les premiers tests de charge. Deux niveaux de cache ont été mis en place : un cache de requête côté WordPress via l'extension WPGraphQL Smart Cache, qui invalide le cache de façon ciblée en fonction des mutations réellement effectuées (une modification de prix n'invalide que les requêtes concernant ce produit, pas tout le catalogue), et un cache de rendu côté Next.js via la régénération statique incrémentale, pour éviter de solliciter WordPress à chaque visite de page produit.

## Ce qui reste imparfait après quatre mois

| Aspect | État après migration |
| --- | --- |
| Lecture du catalogue | Stable, temps de réponse divisé par trois |
| Recherche et filtres facettés | Nécessite une extension complémentaire, non couverte nativement |
| Invalidation du cache sur mutation en masse (import CSV) | Ponctuellement défaillante, surveillée manuellement |
| Panier et checkout | Volontairement laissés sur la Store API classique |

> Le headless ne supprime pas la complexité, il la déplace vers la couche de cache et de résolution de données. Un catalogue rapide en façade cache toujours, quelque part, un travail sérieux d'optimisation des requêtes sous-jacentes.

## Pour aller plus loin

La migration a atteint son objectif principal — un catalogue nettement plus rapide à l'affichage — mais elle a demandé un investissement d'ingénierie que peu de projets anticipent correctement au moment du devis initial. Le conseil le plus utile à donner à une agence qui envisage ce chemin : traiter le problème des variations et du chargement par lots comme un chantier à part entière dès la phase de cadrage, pas comme un détail d'implémentation à régler en cours de route.
