D’abord : quelle lenteur, et à quel endroit ?

« Le site est lent » ne veut rien dire tant qu’on n’a pas séparé trois choses très différentes.

La lenteur du serveur

C’est le temps que met votre hébergement à produire la page — le TTFB, time to first byte. On le lit dans l’onglet Réseau du navigateur, sur la première requête HTML. En dessous de 300 ms sur une page mise en cache, vous êtes bien. Au-dessus de 800 ms sur une page produit, il y a un problème de PHP, de base de données ou d’hébergement, et aucune optimisation d’images ne le compensera.

La lenteur d’affichage

Le serveur répond vite mais la page met du temps à devenir utilisable : trop de CSS et de JavaScript, images non dimensionnées, polices bloquantes, scripts tiers. C’est le domaine des Core Web Vitals.

La lenteur du back-office

Souvent la plus révélatrice. Si la liste des commandes met dix secondes à s’afficher, si l’enregistrement d’un produit prend vingt secondes, le problème est côté base de données ou extensions — et il touche aussi vos visiteurs, en silence.

Précisez également quand : en permanence, aux heures de pointe, la première visite seulement, ou pendant une synchronisation. Une lenteur qui n’apparaît qu’à 20 h désigne un problème de ressources serveur ; une lenteur permanente sur les catégories désigne des requêtes SQL.

Le socle : hébergement, version de PHP, cache objet

C’est la première chose que je regarde, parce que c’est la plus déterminante et la plus rapide à vérifier. Tout se trouve dans WooCommerce → État.

L’hébergement

Une boutique n’est pas un site vitrine. Le panier, le checkout et « Mon compte » ne peuvent pas être mis en cache : chaque visiteur en cours d’achat déclenche une exécution PHP complète et plusieurs dizaines de requêtes SQL. Sur un mutualisé d’entrée de gamme, cela suffit à saturer. Un hébergement infogéré WordPress, ou un VPS correctement configuré, change l’ordre de grandeur.

PHP et OPcache

Visez PHP 8.1 au minimum, 8.2 ou 8.3 de préférence. Le gain entre une vieille version 7.x et une 8.x est réel et gratuit. Vérifiez aussi que OPcache est activé : sans lui, PHP recompile tous les fichiers à chaque requête, et WooCommerce en compte beaucoup. Côté limites, memory_limit à 256 Mo (512 Mo sur un gros catalogue), max_input_vars à 3000 au moins si vous gérez des variations.

Le cache objet persistant

C’est le réglage le plus rentable et le plus souvent absent. Sans cache objet persistant, WordPress refait à chaque requête les mêmes requêtes SQL coûteuses. Avec Redis ou Memcached et l’extension correspondante, elles sont servies depuis la mémoire. Sur une boutique avec beaucoup d’options et de métadonnées, la différence sur le back-office est immédiate. Pensez à définir WP_CACHE_KEY_SALT dans wp-config.php si plusieurs sites partagent la même instance Redis, sinon les caches se mélangent.

Ces trois points relèvent de l’optimisation de la performance au sens strict : on ne touche pas encore au code du site.

La base de données : autoload, révisions, commandes et HPOS

C’est la cause de lenteur la plus sournoise, parce qu’elle est invisible depuis l’interface.

Les options en chargement automatique

WordPress charge à chaque requête toutes les lignes de wp_options marquées en chargement automatique. Des extensions désinstallées laissent leurs réglages derrière elles, des journaux entiers y sont parfois stockés. Au-delà de 800 Ko à 1 Mo cumulés, cela se sent. La requête que je lance en premier :

wp db query "SELECT option_name, LENGTH(option_value) AS taille FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on') ORDER BY taille DESC LIMIT 20;"

Attention : depuis WordPress 6.6, la colonne autoload n’utilise plus seulement yes et no, mais aussi on, off, auto, auto-on et auto-off. Une requête écrite pour l’ancien schéma passe à côté de la moitié des lignes. Ce qui sort en tête est presque toujours identifiable : on supprime ce qui appartient à une extension absente, on bascule le reste hors chargement automatique quand c’est sûr.

Révisions, transients, tables orphelines

Les révisions d’articles et de produits s’accumulent sans limite par défaut. On plafonne avec define( 'WP_POST_REVISIONS', 5 ); dans wp-config.php, puis on purge l’existant. Les transients expirés restent en base tant que rien ne les nettoie : wp transient delete --expired. Les extensions désinstallées laissent aussi leurs tables — elles ne ralentissent pas les requêtes, mais elles alourdissent les sauvegardes et brouillent le diagnostic.

HPOS

Le stockage haute performance des commandes (HPOS) sort les commandes de wp_posts et wp_postmeta pour les placer dans des tables dédiées : wp_wc_orders, wp_wc_order_addresses, wp_wc_order_operational_data, wp_wc_orders_meta. Sur une boutique qui a des dizaines de milliers de commandes, le back-office devient un autre outil. Le réglage se trouve dans WooCommerce → Réglages → Avancé → Fonctionnalités.

Mais on ne bascule pas à la légère : une extension ancienne qui lit encore les commandes dans wp_posts ne verra plus rien. Je vérifie la compatibilité déclarée de chaque extension, j’active d’abord le mode de compatibilité (synchronisation des deux stockages), je teste en préproduction, puis je bascule et je désactive la synchronisation — la garder indéfiniment revient à écrire deux fois chaque commande.

Extensions, Action Scheduler et tâches planifiées

Le nombre d’extensions compte moins que leur qualité, mais passé une trentaine, la probabilité qu’une seule d’entre elles plombe le site devient élevée.

Les extensions qui coûtent cher

Les profils typiques : les extensions de filtres à facettes qui génèrent des requêtes sur wp_postmeta sans index utilisable, les extensions de champs produit supplémentaires qui ajoutent des dizaines de métadonnées par article, les extensions de statistiques qui écrivent en base à chaque page vue, les extensions de sécurité qui analysent chaque requête, et les modules de « related products » qui calculent leurs recommandations à la volée. Une extension bien écrite met ses résultats en cache ; beaucoup ne le font pas.

Action Scheduler

WooCommerce s’appuie sur Action Scheduler pour tout ce qui se fait en arrière-plan : emails, webhooks, synchronisations, mises à jour de stock, tâches des extensions. Les actions vivent dans wp_actionscheduler_actions et se consultent dans WooCommerce → État → Actions planifiées.

Deux pannes classiques. D’abord la saturation : des dizaines de milliers d’actions en attente, souvent parce qu’une extension en crée plus que le système n’en traite. Ensuite l’engorgement par échec : des actions qui échouent, se replanifient, et occupent les cycles. Dans les deux cas, chaque passage de cron consomme du PHP et de la base au détriment des visiteurs.

Je regarde le nombre d’actions en attente et en échec, j’identifie le hook responsable (le nom de l’action désigne presque toujours l’extension), puis je traite la cause. Le nettoyage des actions terminées se fait avec wp action-scheduler clean, et on peut forcer le traitement de la file avec wp action-scheduler run.

WP-Cron

Par défaut, WP-Cron se déclenche sur les visites : sur un site peu fréquenté les tâches prennent du retard, sur un site très fréquenté elles se déclenchent trop souvent. La bonne configuration sur une boutique : define( 'DISABLE_WP_CRON', true ); dans wp-config.php, et une vraie tâche cron serveur qui appelle wp cron event run --due-now chaque minute.

Le front : cache de page, fragments AJAX, images, constructeur

Le cache de page, et surtout ses exclusions

Un cache de page bien réglé sert l’accueil, les catégories et les fiches produit en quelques dizaines de millisecondes. Mais le panier, la commande (checkout) et « Mon compte » ne doivent jamais être mis en cache — sinon un client voit le panier d’un autre, ou un panier vide. Il faut exclure ces pages, mais aussi les cookies WooCommerce (woocommerce_cart_hash, woocommerce_items_in_cart, wp_woocommerce_session_) et les requêtes ?wc-ajax=. Si vous utilisez les blocs panier et checkout, excluez également les routes de la Store API (/wp-json/wc/store/). La même règle s’applique au CDN placé devant le site.

Les fragments du panier

Le script wc-cart-fragments rafraîchit le compteur du panier via une requête AJAX ?wc-ajax=get_refreshed_fragments. Cette requête contourne le cache de page et exécute PHP à chaque chargement : sur un hébergement lent, elle retarde l’affichage sur tout le site. Sur une boutique où le mini-panier n’est pas indispensable en dehors des pages de vente, la désactiver ailleurs qu’au panier et sur les fiches produit fait gagner une charge serveur significative. C’est un arbitrage, pas une évidence : on le mesure.

Images

Les images restent le premier poste de poids d’une fiche produit. Format moderne (WebP ou AVIF), dimensions réelles proches des dimensions d’affichage, loading="lazy" pour tout sauf l’image principale — celle-ci doit au contraire être chargée en priorité (fetchpriority="high") puisqu’elle est le plus souvent l’élément LCP. Après un changement de thème, wp media regenerate évite de servir des miniatures aux mauvaises tailles.

Le constructeur de pages

Un constructeur appliqué au gabarit produit et au tunnel d’achat est un mauvais calcul : on charge le plus de code là où chaque dixième de seconde a une valeur commerciale. Le sujet mérite un article à part — j’y consacre celui sur WordPress lent avec un constructeur de pages. Mon parti pris sur WooCommerce : des blocs natifs et un thème léger pour les pages de vente, quitte à garder le constructeur sur les pages éditoriales.

Variations et catalogues larges

Une fiche produit avec plusieurs centaines de variations charge par défaut toutes les combinaisons dans la page. WooCommerce bascule automatiquement en mode AJAX au-delà d’un certain seuil, réglable via le filtre woocommerce_ajax_variation_threshold. Si vos fiches à fortes variations sont nettement plus lentes que les autres, c’est le premier endroit à regarder.

Diagnostiquer : les outils, et l’ordre dans lequel je les utilise

Je ne modifie rien avant d’avoir une mesure. Voici la séquence.

1. Mesurer le TTFB, page par page

Accueil, catégorie, fiche produit, panier, checkout, back-office. Avec et sans session (navigation privée contre compte connecté). Cette simple grille sépare déjà un problème de cache d’un problème de serveur : si l’accueil répond en 150 ms et la fiche produit en 3 s, ce n’est pas l’hébergement.

2. Query Monitor

C’est l’outil central. Installé en préproduction — ou en production avec un accès restreint —, il affiche pour chaque page le nombre de requêtes SQL, leur durée, quelle extension les a déclenchées, le temps PHP, la mémoire, les appels HTTP sortants et les hooks lents. Une page qui fait 1 800 requêtes a un problème identifiable en trente secondes avec Query Monitor, et en trois jours sans.

Si vous voulez le détail des requêtes hors Query Monitor, define( 'SAVEQUERIES', true ); enregistre tout dans $wpdb->queries — à n’activer que temporairement, cela consomme de la mémoire.

3. Le journal des requêtes lentes MySQL

Quand la lenteur est intermittente, c’est le seul moyen honnête de savoir. On active le slow query log côté serveur avec un seuil bas (long_query_time = 1, voire 0,5), on laisse tourner une journée, on lit ce qui ressort. Les coupables sont souvent des meta_query sur wp_postmeta, générées par un filtre ou un export.

4. Le profilage PHP

Pour les cas résistants : un profileur applicatif (type New Relic ou Blackfire) ou Xdebug en préproduction donne la pile d’appels réelle et la fonction qui consomme. C’est plus lourd à mettre en place, mais c’est ce qui tranche entre deux hypothèses.

5. La bisection des extensions

En dernier recours seulement, et jamais en production : sur une copie du site, on désactive la moitié des extensions, on mesure, on recoupe. C’est efficace mais coûteux en temps — d’où l’intérêt de commencer par Query Monitor, qui désigne souvent directement le coupable.

Les erreurs à éviter, et la méthode que je recommande

Ce qui fait perdre du temps

  • Empiler les extensions d’optimisation. Deux extensions de cache installées en même temps se neutralisent ou créent des bugs d’affichage. Une seule, bien réglée.
  • Activer toutes les options de minification et de différé d’un coup. C’est la meilleure façon de casser le panier ou le checkout sans savoir quelle option est responsable. On active une option, on teste un achat complet, on passe à la suivante.
  • Optimiser les images alors que le TTFB est à 3 secondes. Effort réel, gain invisible.
  • Croire aux notes d’outils synthétiques. Un score de 95 sur un test de laboratoire ne dit rien de ce que vivent vos clients sur mobile en 4G. Les données terrain comptent davantage.
  • Tester en production. Sur une boutique qui encaisse, chaque essai a un coût potentiel en commandes perdues.

La méthode

  1. Établir une mesure de départ page par page, TTFB et rendu, mobile et bureau.
  2. Vérifier le socle : hébergement, PHP, OPcache, cache objet. Corriger ce qui est manifestement sous-dimensionné.
  3. Nettoyer la base : autoload, révisions, transients, et statuer sur HPOS.
  4. Profiler avec Query Monitor les trois pages les plus lentes. Traiter l’extension ou la requête dominante.
  5. Régler le cache de page et ses exclusions, vérifier un achat complet après chaque changement.
  6. Seulement ensuite : images, polices, scripts tiers, chargement différé.
  7. Remesurer, documenter, et inscrire l’entretien dans la durée — c’est l’objet de la maintenance.

Une seule modification à la fois, une mesure après chaque modification. C’est lent à lire, c’est rapide à faire, et c’est la seule façon de savoir ce qui a produit le gain. Si vous préférez déléguer cette phase d’analyse, c’est exactement ce que couvre le diagnostic e-commerce, et j’en détaille les suites sur ma page expert WooCommerce.

Checklist

  • Mesurer le TTFB sur l’accueil, une catégorie, une fiche produit, le panier, le checkout et le back-office, avant toute modification.
  • Vérifier la version de PHP (8.1 minimum), l’activation d’OPcache et le memory_limit dans WooCommerce → État.
  • Mettre en place un cache objet persistant (Redis ou Memcached) si la boutique n’en a pas.
  • Lister les vingt plus grosses options en chargement automatique et supprimer celles des extensions absentes.
  • Plafonner les révisions avec WP_POST_REVISIONS et purger les transients expirés.
  • Contrôler l’état de HPOS et la compatibilité des extensions avant toute bascule, en préproduction.
  • Vérifier le nombre d’actions en attente et en échec dans WooCommerce → État → Actions planifiées.
  • Désactiver WP-Cron au profit d’une vraie tâche cron serveur appelant wp cron event run --due-now.
  • Exclure du cache de page et du CDN le panier, le checkout, « Mon compte », les cookies WooCommerce et les requêtes ?wc-ajax=.
  • Profiler les pages les plus lentes avec Query Monitor et traiter la cause dominante avant tout le reste.

Questions fréquentes

Combien d’extensions est-ce trop pour WooCommerce ?

Il n’y a pas de seuil magique : une boutique avec quarante extensions bien écrites peut être plus rapide qu’une boutique qui en compte douze dont une catastrophique. Ce qui compte, c’est le coût réel de chacune, que Query Monitor mesure page par page. Cela dit, au-delà de trente extensions, la surface de conflit et le travail de mise à jour deviennent eux-mêmes un problème de maintenance.

Activer HPOS va-t-il accélérer ma boutique ?

Nettement, si vous avez beaucoup de commandes : le back-office et toutes les requêtes liées aux commandes s’allègent. En revanche, si votre lenteur vient des pages catégories ou du front, HPOS n’y changera rien. Et la bascule se prépare : vérification de la compatibilité de chaque extension, mode de compatibilité activé, test en préproduction, puis bascule et arrêt de la synchronisation.

Mon hébergeur dit que le problème vient de mon site. Comment trancher ?

En mesurant. Si le TTFB d’une page statique mise en cache est déjà élevé, ou si les mêmes requêtes SQL prennent dix fois plus de temps aux heures de pointe, le serveur est en cause. Si une fiche produit déclenche 1 500 requêtes SQL quand l’accueil en fait 60, c’est le site. Query Monitor et le journal des requêtes lentes répondent à cette question en une journée.

Faut-il désactiver les fragments du panier ?

Pas systématiquement. La requête AJAX de rafraîchissement du panier contourne le cache de page et coûte une exécution PHP sur chaque chargement, ce qui pèse surtout sur un hébergement modeste. Si votre mini-panier n’est utile que sur les pages de vente, limiter le script à ces pages est un bon compromis. Sur une boutique où l’ajout au panier se fait depuis les listes produit, le désactiver casse l’expérience : on mesure avant de décider.

Une extension de cache suffit-elle à régler le problème ?

Elle masque souvent le symptôme sur les pages publiques sans traiter la cause. Le panier, le checkout, « Mon compte » et le back-office ne sont pas mis en cache : ils continueront de subir la lenteur réelle du serveur et de la base. Et un cache mal configuré sur une boutique crée des bugs bien plus graves qu’une lenteur, comme un panier servi au mauvais client.