Si votre page d’accueil met 5 secondes à s’afficher alors que votre hébergeur vous jure que “tout est OK”, le problème est presque toujours visible… dans les requêtes SQL, les hooks (actions/filtres) ou les scripts front qui tournent trop tôt/trop souvent.
Contexte: avril 2026, WordPress 6.9.4 et PHP 8.1+. Tout ce qui suit cible ces versions. Si vous tombez sur un vieux tutoriel qui vous parle de constantes ou de hooks “magiques”, méfiez-vous: j’ai souvent vu des snippets de 2018 casser des sites modernes (ou désactiver le cache sans s’en rendre compte).
Le problème de performance
Les symptômes typiques:
- TTFB élevé (Time To First Byte): le serveur met trop de temps à répondre avant même d’envoyer le HTML.
- INP/LCP mauvais (Core Web Vitals): interaction lente, ou élément principal qui apparaît tard (souvent à cause de JS/CSS trop lourds).
- Back-office lent (éditeur, liste des articles, WooCommerce): souvent lié à des requêtes SQL, des autoload options, ou des hooks admin trop gourmands.
L’impact est concret: baisse SEO (Google pénalise les UX médiocres), hausse du taux de rebond, et fatigue côté administration (vous publiez moins, vous corrigez moins, vous procrastinez plus).
À la fin, vous saurez:
- Mesurer où WordPress perd du temps (SQL, PHP, HTTP, JS/CSS).
- Utiliser Query Monitor et WP_DEBUG sans transformer votre site en sapin de Noël de warnings.
- Corriger des causes fréquentes: requêtes non indexées, hooks exécutés 200 fois, scripts chargés partout, absence de cache objet, transients mal utilisés.
Sauvegardez avant de modifier. Ne modifiez jamais le core WordPress. Si vous collez du code: faites-le dans un plugin custom ou un mu-plugin (recommandé pour les snippets de diagnostic), ou dans le functions.php d’un thème enfant si c’est lié au thème.
Résumé rapide
- Installez Query Monitor et cherchez: requêtes lentes, hooks lents, HTTP API, scripts/styles.
- Activez WP_DEBUG + WP_DEBUG_LOG (sans afficher en front) pour repérer warnings/fatal qui ralentissent.
- Ajoutez un mini-profiler (mu-plugin) pour mesurer des blocs précis (template, shortcode, widget).
- Corrigez d’abord: SQL (requêtes répétées, meta_query lourdes), puis hooks, puis JS/CSS.
- Terminez par le cache (objet + page) et une config serveur propre (OPcache, logs, compression).
Diagnostic avec du code
Prérequis
- WordPress 6.9.4, PHP 8.1+
- Accès admin WordPress
- Accès FTP/SFTP (ou gestionnaire de fichiers) pour créer un mu-plugin
- Idéalement: accès SSH pour WP-CLI
Query Monitor: ce qu’il mesure exactement
Query Monitor est un plugin de diagnostic qui affiche (pour les admins) des infos sur:
- Les requêtes SQL (temps, stack trace, répétitions).
- Les hooks WordPress (une action exécute du code; un filtre modifie une valeur).
- Les scripts/styles enqueued, et les dépendances.
- Les appels HTTP via l’API HTTP WordPress.
Installez-le depuis le dépôt officiel: Query Monitor (wordpress.org).
Astuce terrain: sur un site lent, je commence par “Queries” puis “Hooks & Actions”. Si vous voyez 300 requêtes SQL sur une page simple, vous avez déjà une piste.
Activer WP_DEBUG proprement (sans casser le front)
WP_DEBUG active l’affichage/collecte des erreurs PHP. Sur un site en production, vous voulez logger sans afficher.
Dans wp-config.php (au-dessus de /* That's all, stop editing! */):
/**
* Debug PHP (WordPress 6.9.4+, PHP 8.1+)
* Objectif: logger sans afficher en front.
*/
define('WP_DEBUG', true);
/** Enregistre dans /wp-content/debug.log */
define('WP_DEBUG_LOG', true);
/** Empêche l'affichage des erreurs sur le site public */
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', '0');
/**
* Optionnel: désactive les notices obsolètes si un vieux plugin spamme les logs.
* À utiliser temporairement, le temps d'identifier le plugin fautif.
*/
// error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);
Pourquoi ça aide la performance: un site qui génère des milliers de warnings (souvent sur PHP 8.1+) perd du temps en I/O (écriture disque) et parfois bloque des buffers. Et surtout, un warning en boucle est souvent le symptôme d’un code qui fait n’importe quoi (requêtes répétées, appels de fonctions sur null, etc.).
Source officielle: Debugging in WordPress (developer.wordpress.org).
Créer un mu-plugin de diagnostic (recommandé)
Un mu-plugin (Must-Use Plugin) est chargé automatiquement, sans dépendre du thème. C’est l’endroit le plus fiable pour un profiler temporaire.
Créez le fichier: wp-content/mu-plugins/bpcab-perf-profiler.php (créez le dossier mu-plugins s’il n’existe pas).
<?php
/**
* Plugin Name: BPCAB - Perf Profiler (temporaire)
* Description: Mini-profiler pour mesurer des zones lentes sans dépendre du thème.
* Author: Vous
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* Mesure la durée totale de génération WordPress (PHP) côté serveur.
* On logge uniquement pour les admins afin d'éviter du bruit inutile.
*/
add_action('shutdown', function () {
if (!current_user_can('manage_options')) {
return;
}
$seconds = timer_stop(0, 6); // Temps depuis le démarrage WP (en secondes)
$memory = size_format(memory_get_peak_usage(true));
error_log('[PERF] Temps total PHP: ' . $seconds . 's | Mémoire pic: ' . $memory);
}, 9999);
/**
* Chronomètre simple pour entourer des blocs de code.
* Utilisation: do_action('bpcab_profiler_mark', 'mon-etape');
*/
add_action('bpcab_profiler_mark', function (string $label) {
static $marks = [];
$now = microtime(true);
if (!isset($marks[$label])) {
$marks[$label] = $now;
return;
}
$elapsed = $now - $marks[$label];
unset($marks[$label]);
if (current_user_can('manage_options')) {
error_log('[PERF] Bloc "' . $label . '" : ' . number_format($elapsed, 4) . 's');
}
});
Vous venez de vous donner un “chronomètre” réutilisable. Dans la suite, je vais m’en servir pour mesurer un shortcode, une requête, ou une portion de template.
Activer le slow query log (MySQL/MariaDB) côté serveur
Quand Query Monitor montre “une requête à 1,2s”, le slow query log vous dit si c’est un problème d’index, de table énorme, ou de disque.
Si vous avez accès à la config MySQL (ou MariaDB), vérifiez la doc officielle: MySQL Slow Query Log.
Sur un hébergement mutualisé, vous n’aurez pas toujours la main. Dans ce cas, Query Monitor + logs PHP suffisent déjà à trouver 80% des causes.
WP-CLI: commandes de diagnostic utiles
WP-CLI vous évite de cliquer partout, et vous donne des infos “brutes”. Sources: WP-CLI Commands (developer.wordpress.org).
# Versions (utile pour repérer un PHP trop ancien ou un WP pas à jour)
wp core version
wp --info
# Plugins lourds: liste + statut
wp plugin list --status=active
# Vérifie les tâches cron (un wp-cron saturé peut ralentir l'admin)
wp cron event list --fields=hook,next_run,interval
# Taille des options autoload (souvent une cause cachée de lenteur)
wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
# Top 20 des options autoload les plus lourdes
wp db query "SELECT option_name, LENGTH(option_value) AS bytes FROM wp_options WHERE autoload='yes' ORDER BY bytes DESC LIMIT 20;"
Si vous découvrez 20–50 MB en autoload, vous avez trouvé un gros morceau. Sur des sites Elementor/Divi/Avada, j’ai vu des options de builders et de plugins de stats gonfler sans limite.
Étape 1 : trouver les requêtes SQL lentes (et les éliminer)
Le cas le plus fréquent: meta_query lourde et non paginée
Beaucoup de thèmes/plugins font des requêtes sur postmeta avec LIKE ou sans index utile. Résultat: table scannée, TTFB qui explose.
AVANT (lent): requête large, pas de limites, meta_query coûteuse
Exemple typique dans un plugin maison ou un functions.php:
<?php
// Exemple lent: à ne pas reproduire tel quel.
add_shortcode('articles_populaires', function () {
// Marqueur profiler
do_action('bpcab_profiler_mark', 'shortcode_articles_populaires');
$q = new WP_Query([
'post_type' => 'post',
'posts_per_page' => -1, // ⚠️ Charge tout
'meta_query' => [
[
'key' => 'views_count',
'value' => 100,
'compare' => '>=',
'type' => 'NUMERIC',
]
],
'orderby' => 'meta_value_num',
'order' => 'DESC',
]);
$html = '<ul>';
foreach ($q->posts as $p) {
$html .= '<li>' . esc_html(get_the_title($p)) . '</li>';
}
$html .= '</ul>';
do_action('bpcab_profiler_mark', 'shortcode_articles_populaires');
return $html;
});
Pourquoi c’est lent:
posts_per_page = -1force WordPress à charger tous les posts correspondants.- La requête sur
postmetaest coûteuse, surtout si la table est grosse. - Vous ne désactivez pas les calculs inutiles (pagination, caches de meta/terms).
APRÈS (optimisé): limiter, éviter le superflu, réduire les jointures
Objectif: moins de lignes SQL, moins de travail PHP. Même résultat “fonctionnel” pour un blog.
<?php
add_shortcode('articles_populaires', function () {
do_action('bpcab_profiler_mark', 'shortcode_articles_populaires');
$q = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 10, // ✅ Limite claire
'no_found_rows' => true, // ✅ Pas de pagination => moins de SQL
'ignore_sticky_posts' => true,
'update_post_meta_cache' => false, // ✅ Évite de charger toute la meta
'update_post_term_cache' => false, // ✅ Évite de charger toutes les taxonomies
'meta_key' => 'views_count',
'orderby' => 'meta_value_num',
'order' => 'DESC',
'meta_query' => [
[
'key' => 'views_count',
'value' => 100,
'compare' => '>=',
'type' => 'NUMERIC',
]
],
]);
$html = '<ul>';
foreach ($q->posts as $p) {
$html .= '<li>' . esc_html(get_the_title($p)) . '</li>';
}
$html .= '</ul>';
do_action('bpcab_profiler_mark', 'shortcode_articles_populaires');
return $html;
});
Mesure attendue (ordre de grandeur): sur un site moyen, j’ai souvent vu ce type de shortcode passer de 0,8–2,5s à 0,05–0,25s simplement en limitant + no_found_rows + caches désactivés.
Où voir la différence:
- Dans
wp-content/debug.log(vos lignes[PERF]). - Dans Query Monitor > “Queries” (temps total SQL et requêtes dominantes).
Requêtes répétées: le “N+1” sur get_post_meta()
Un autre classique: une boucle de 50 posts, et à l’intérieur 5 appels à get_post_meta(). Ça peut déclencher des dizaines/centaines de requêtes si le cache objet n’est pas là.
AVANT (lent)
<?php
foreach ($posts as $p) {
// ⚠️ Appels répétés, souvent non groupés
$price = get_post_meta($p->ID, '_price', true);
$sku = get_post_meta($p->ID, '_sku', true);
$brand = get_post_meta($p->ID, '_brand', true);
echo esc_html($price . ' - ' . $sku . ' - ' . $brand);
}
APRÈS (optimisé): précharger la meta une fois
WordPress sait précharger les metas d’une liste de posts via update_meta_cache().
<?php
$post_ids = array_map(static function ($p) {
return (int) $p->ID;
}, $posts);
// ✅ Précharge la meta pour tous les posts d'un coup
update_meta_cache('post', $post_ids);
foreach ($posts as $p) {
$price = get_post_meta($p->ID, '_price', true);
$sku = get_post_meta($p->ID, '_sku', true);
$brand = get_post_meta($p->ID, '_brand', true);
echo esc_html($price . ' - ' . $sku . ' - ' . $brand);
}
Avec Query Monitor, vous verrez souvent le nombre de requêtes chuter. Sur un site sans cache objet persistant, c’est parfois spectaculaire.
Référence: update_meta_cache() (developer.wordpress.org).
Étape 2 : traquer les hooks lents et les appels répétés
Un hook est un point d’extension de WordPress. Il y a:
- Les actions: “faites quelque chose maintenant” (ex:
init,wp_enqueue_scripts). - Les filtres: “modifiez cette valeur” (ex:
the_content,excerpt_length).
Quand un site est lent, je vois souvent:
- Du code accroché à
initqui n’a rien à faire là (et tourne sur chaque requête). - Des filtres sur
the_contentqui parsèrent du HTML complet à chaque affichage. - Des appels HTTP (API externes) déclenchés en front.
Profiler un hook (sans plugin supplémentaire)
Ajoutez ce code dans votre mu-plugin (le même fichier que plus haut). Il mesure le temps passé dans un hook précis.
<?php
/**
* Mesure la durée d'exécution d'un hook donné.
* Exemple: the_content est souvent un coupable.
*/
function bpcab_profile_hook(string $hook_name): void
{
add_action($hook_name, function () use ($hook_name) {
do_action('bpcab_profiler_mark', 'hook_' . $hook_name);
}, 0);
add_action($hook_name, function () use ($hook_name) {
do_action('bpcab_profiler_mark', 'hook_' . $hook_name);
}, 9999);
}
add_action('plugins_loaded', function () {
if (!current_user_can('manage_options')) {
return;
}
// Hooks à surveiller (ajoutez/retirez selon vos symptômes)
bpcab_profile_hook('init');
bpcab_profile_hook('wp_enqueue_scripts');
bpcab_profile_hook('the_content');
});
Ensuite, chargez une page et consultez debug.log. Si hook_the_content prend 0,7s, vous savez où creuser.
Cas réel: filtre the_content qui fait une requête externe à chaque page
J’ai souvent croisé ça sur des sites qui affichent “la météo”, “un compteur”, “un flux Instagram” via une API. Le développeur a mis l’appel dans the_content. Résultat: chaque page attend l’API.
AVANT (lent)
<?php
add_filter('the_content', function ($content) {
// ⚠️ Très mauvais: appel HTTP à chaque affichage
$response = wp_remote_get('https://api.exemple.test/widget');
if (is_wp_error($response)) {
return $content;
}
$body = wp_remote_retrieve_body($response);
return $content . '<div class="widget-api">' . wp_kses_post($body) . '</div>';
});
APRÈS (optimisé): cache via transient + timeout serré
Un transient est un cache clé/valeur avec expiration. Parfait pour éviter de retaper une API.
<?php
add_filter('the_content', function ($content) {
// ✅ Ne faites ça que sur le front (pas admin, pas REST)
if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
return $content;
}
$cache_key = 'bpcab_widget_api_html';
$html = get_transient($cache_key);
if ($html === false) {
$args = [
'timeout' => 2, // ✅ Timeout court pour éviter de bloquer le rendu
];
$response = wp_remote_get('https://api.exemple.test/widget', $args);
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$body = wp_remote_retrieve_body($response);
$html = '<div class="widget-api">' . wp_kses_post($body) . '</div>';
// ✅ Cache 10 minutes
set_transient($cache_key, $html, 10 * MINUTE_IN_SECONDS);
} else {
$html = '';
// ✅ Cache court d'échec pour éviter de retenter 50 fois si l'API tombe
set_transient($cache_key, $html, 1 * MINUTE_IN_SECONDS);
}
}
return $content . $html;
}, 20);
Références:
Mesure attendue: si l’API prenait 400 ms, vous récupérez quasiment tout ce temps sur 9 pages sur 10 (cache “hit”).
Étape 3 : charger moins de JS/CSS inutile (defer/async/preload)
Quand le TTFB est correct mais que le site “semble” lent, c’est souvent un problème de JS/CSS: trop de fichiers, trop gros, ou chargés partout.
Compatibilité page builders:
- Elementor, Divi 5, Avada chargent souvent des assets globaux. Vous pouvez améliorer sans casser le builder, mais allez-y progressivement.
- Évitez de “désenregistrer au hasard” des scripts du builder: commencez par vos plugins (sliders, tracking, widgets).
Mesurer ce qui est réellement enqueued
Ajoutez ce snippet en mu-plugin pour logguer les handles en front. Très pratique quand Query Monitor est trop verbeux.
<?php
add_action('wp_enqueue_scripts', function () {
if (!current_user_can('manage_options')) {
return;
}
global $wp_scripts, $wp_styles;
$script_handles = array_keys((array) $wp_scripts->queue);
$style_handles = array_keys((array) $wp_styles->queue);
error_log('[PERF] Scripts enqueued: ' . implode(', ', $script_handles));
error_log('[PERF] Styles enqueued: ' . implode(', ', $style_handles));
}, 9999);
Désactiver un script inutile sur la majorité des pages
Exemple: un plugin de formulaire charge son JS sur tout le site, alors que vous n’avez un formulaire que sur /contact.
APRÈS (optimisé): dequeue conditionnel
Collez dans un plugin custom ou mu-plugin. Remplacez les handles par les vôtres (vous les trouvez via Query Monitor > Scripts).
<?php
add_action('wp_enqueue_scripts', function () {
// ⚠️ Ne faites pas ça dans l'admin
if (is_admin()) {
return;
}
// Exemple: ne garder le script du formulaire que sur la page "Contact"
if (!is_page('contact')) {
// Remplacez 'mon-plugin-form' par le handle réel
wp_dequeue_script('mon-plugin-form');
wp_dequeue_style('mon-plugin-form');
}
}, 100);
Pourquoi ça aide Core Web Vitals: moins de JS à parser/exécuter améliore souvent l’INP. Et moins de CSS bloquant améliore le LCP.
Ajouter defer (avec prudence) sur un script spécifique
async et defer changent l’ordre d’exécution. Si vous appliquez ça à un script dont un autre dépend, vous cassez le site. Je le fais uniquement sur des scripts isolés (analytics, widgets non critiques).
<?php
/**
* Ajoute defer à un handle précis.
* Testez sur staging: un mauvais defer peut casser un slider ou un menu.
*/
add_filter('script_loader_tag', function ($tag, $handle, $src) {
$defer_handles = [
'mon-analytics',
'widget-non-critique',
];
if (in_array($handle, $defer_handles, true)) {
// On reconstruit une balise propre avec defer
$tag = '<script src="' . esc_url($src) . '" defer></script>' . "n";
}
return $tag;
}, 10, 3);
Référence: script_loader_tag (developer.wordpress.org).
Précharger une police (preload) sans plugin
Si votre LCP dépend d’une police, le preload peut aider. Mais ne préchargez pas 10 fonts. Une ou deux maximum.
<?php
add_action('wp_head', function () {
if (is_admin()) {
return;
}
// Exemple: adaptez l'URL à votre thème
$font_url = get_stylesheet_directory_uri() . '/assets/fonts/inter-var.woff2';
echo '<link rel="preload" href="' . esc_url($font_url) . '" as="font" type="font/woff2" crossorigin>' . "n";
}, 2);
Étape 4 : cache objet, transients et cache page
Sans cache, WordPress refait le même travail à chaque visite. Il y a deux couches différentes:
- Cache objet (object cache): met en cache des résultats (options, requêtes, objets) en mémoire. Persistant si Redis/Memcached est configuré.
- Cache page: met en cache le HTML final. C’est celui qui fait le plus gros effet sur le TTFB en front.
Vérifier si un cache objet persistant est actif
Ajoutez ce snippet (mu-plugin) pour logguer le statut.
<?php
add_action('init', function () {
if (!current_user_can('manage_options')) {
return;
}
// wp_using_ext_object_cache() existe depuis longtemps et reste pertinent
if (wp_using_ext_object_cache()) {
error_log('[PERF] Cache objet persistant: OUI');
} else {
error_log('[PERF] Cache objet persistant: NON');
}
});
Référence: wp_using_ext_object_cache() (developer.wordpress.org).
Micro-cache applicatif: mettre en cache un rendu HTML coûteux
Même avec un cache page, certaines pages dynamiques (ou l’admin) gagnent à cacher des fragments.
APRÈS: cache de fragment via transient
<?php
function bpcab_render_sidebar_bloc_populaire(): string
{
$cache_key = 'bpcab_sidebar_bloc_populaire_v1';
$html = get_transient($cache_key);
if ($html !== false) {
return $html;
}
do_action('bpcab_profiler_mark', 'render_sidebar_bloc_populaire');
$q = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 5,
'no_found_rows' => true,
'ignore_sticky_posts' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
]);
ob_start();
echo '<div class="sidebar-populaire"><ul>';
foreach ($q->posts as $p) {
echo '<li>' . esc_html(get_the_title($p)) . '</li>';
}
echo '</ul></div>';
$html = (string) ob_get_clean();
do_action('bpcab_profiler_mark', 'render_sidebar_bloc_populaire');
// ✅ Cache 15 min
set_transient($cache_key, $html, 15 * MINUTE_IN_SECONDS);
return $html;
}
Où coller ce code: plugin custom ou functions.php du thème enfant. Si vous utilisez Divi 5/Elementor/Avada, vous pouvez l’exposer via shortcode et l’insérer dans un module HTML/Shortcode du builder.
Configuration serveur
Ici on parle “gains gratuits” quand c’est bien configuré. Si vous n’avez pas la main, demandez au support de l’hébergeur en copiant-collant les extraits.
wp-config.php: limiter le bruit et protéger la prod
En production, gardez le debug log seulement le temps du diagnostic. Ensuite, repassez WP_DEBUG à false.
<?php
// Production (après diagnostic)
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
Si vous laissez WP_DEBUG_LOG actif sur un site très fréquenté, le fichier debug.log peut grossir et devenir un problème de performance… ou de sécurité (il peut contenir des chemins, messages d’erreur, etc.).
php.ini: OPcache (souvent oublié)
OPcache accélère PHP en gardant le bytecode en mémoire. Sans OPcache, vous perdez du temps à recompiler les scripts.
Référence: OPcache configuration (php.net).
; php.ini (exemples, adaptez à votre hébergeur)
opcache.enable=1
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=2
.htaccess (Apache): compression + cache navigateur (basique)
Sur Apache, vous pouvez activer Brotli/Gzip selon modules disponibles. Exemple minimal (sans promesse: ça dépend de l’hébergeur).
# .htaccess (Apache)
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css application/javascript application/json image/svg+xml
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 7 days"
ExpiresByType application/javascript "access plus 7 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/png "access plus 30 days"
ExpiresByType image/jpeg "access plus 30 days"
ExpiresByType image/webp "access plus 30 days"
</IfModule>
Si vous êtes sur Nginx, ça se configure ailleurs (pas dans WordPress). Dans ce cas, demandez une config équivalente.
Vérification des résultats
Mesurer côté WordPress (logs du profiler)
Après chaque changement, rechargez 3 fois la même page (la première charge chauffe les caches). Puis comparez les lignes [PERF] dans wp-content/debug.log.
- Objectif réaliste en front (blog): Temps total PHP < 0,3–0,8s sur page cacheable.
- Objectif admin: plus variable, mais évitez les pages à 3–5s “sans raison”.
Mesurer en HTTP (TTFB) avec curl
Le TTFB est influencé par le réseau, mais vous verrez vite si votre cache page fait son travail.
# Remplacez l'URL
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}nTotal: %{time_total}n" https://exemple.com/
Si le TTFB baisse fortement après mise en place du cache page/CDN, vous êtes sur la bonne voie.
Vérifier le nombre de requêtes SQL
Avec Query Monitor, surveillez:
- Nombre total de requêtes
- Temps total SQL
- Requêtes “dupliquées”
Astuce: si vous avez “peu de requêtes mais lentes”, pensez index/DB. Si vous avez “beaucoup de requêtes rapides”, pensez N+1 et caches.
Si les performances ne s’améliorent pas
Checklist technique (par ordre d’efficacité)
- Désactivez temporairement (sur une copie/staging) les plugins non essentiels et remesurez. Un seul plugin peut doubler le TTFB.
- Vérifiez autoload options (WP-CLI plus haut). Si c’est énorme, nettoyez (avec prudence).
- Regardez Query Monitor > HTTP API Calls. Un appel externe lent ruine tout.
- Regardez Query Monitor > Hooks. Un hook à 1s est un coupable net.
- Vérifiez si vous avez un cache page actif. Sans cache page, un site à fort trafic souffre vite.
- Vérifiez OPcache, et la charge CPU/RAM.
Un diagnostic “caché” très fréquent: wp-cron qui s’exécute trop souvent
Si votre site déclenche des tâches cron lourdes à chaque visite, vous pouvez voir des lenteurs aléatoires.
Test via WP-CLI:
wp cron event list --fields=hook,next_run,interval | head -n 30
Si vous voyez des hooks qui tournent chaque minute alors qu’ils devraient tourner chaque heure, cherchez le plugin responsable.
Builders (Divi 5 / Elementor / Avada): points de friction typiques
- Pages construites très lourdes: beaucoup de modules, sliders, animations. Le TTFB peut être OK, mais le LCP/INP souffre.
- Plugins “addons” de builder: ils ajoutent souvent 5–20 scripts globaux.
- Fonts/Icons multiples: Font Awesome + packs + Google Fonts en double.
Approche sûre: commencez par désenfiler les scripts des plugins tiers, pas ceux du builder. Puis mesurez. Ensuite seulement, optimisez les pages les plus vues.
Pièges et erreurs courantes
Erreurs que je vois tout le temps (et qui font perdre des heures)
- Coller un snippet dans le mauvais fichier (ex: dans le thème parent au lieu du thème enfant).
- Oublier un
;et provoquer une erreur fatale (site blanc). Travaillez sur staging, ou ayez un accès FTP prêt. - Utiliser un hook inadapté (ex: faire des
wp_dequeue_scriptsurinitau lieu dewp_enqueue_scripts). - Tester avec le cache activé sans le vider, puis conclure “ça ne change rien”.
- Activer
WP_DEBUG_DISPLAYen production: vous affichez des infos sensibles et vous cassez parfois la mise en page. - Appliquer
deferà un script critique (menu, slider), puis “le site est cassé sur mobile”. - Suivre un vieux tuto qui recommande des constantes obsolètes ou des hacks de minification agressifs.
Tableau de diagnostic
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| TTFB > 1s sur toutes les pages | Pas de cache page, SQL lente, PHP sans OPcache | curl TTFB + Query Monitor (temps SQL) + vérifier OPcache | Activer cache page/CDN, corriger requêtes, activer OPcache |
| Admin (wp-admin) très lent | Hooks admin lourds, wp-cron saturé, autoload options énorme | Query Monitor > Hooks, WP-CLI autoload, wp cron event list | Désactiver/optimiser plugin fautif, réduire autoload, corriger cron |
| Beaucoup de requêtes (200+) | N+1 via get_post_meta/get_term, boucles mal optimisées | Query Monitor > Queries (dupliquées), profiler shortcode | Limiter WP_Query, no_found_rows, update_meta_cache, cache fragments |
| LCP mauvais mais TTFB correct | CSS bloquant, images héro non optimisées, fonts lentes | Query Monitor > Scripts/Styles + audit CWV | Dequeue ciblé, preload font critique, réduire CSS/JS, lazy-load images |
| INP mauvais (site “lourd” au clic) | Trop de JS, scripts tiers, animations builder | Liste handles scripts + tests terrain mobile | Retirer scripts inutiles, defer sur non critiques, réduire modules/animations |
| Lenteur aléatoire (1 page sur 5) | Appels HTTP externes, cron qui se déclenche en front | Query Monitor > HTTP API Calls, logs profiler | Transients + timeout court, déplacer cron en vrai cron serveur |
Conseils de maintenance
Gardez Query Monitor pour les admins, pas pour les visiteurs
Query Monitor est fait pour diagnostiquer. Sur des sites à fort trafic, je l’installe, je corrige, puis je le désactive. Pas parce qu’il “ralentit tout”, mais parce que vous n’avez pas besoin d’un outil de debug en permanence.
Automatiser une vérification “autoload options” mensuelle
Sur des sites qui vivent (plugins installés/désinstallés), l’autoload gonfle. Faites un rappel mensuel et notez la valeur.
wp db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
Surveillez les logs, mais évitez l’emballement
- Activez
WP_DEBUG_LOGuniquement pendant le diagnostic. - Supprimez/archivez
wp-content/debug.logaprès. - Corrigez la source (plugin/thème) au lieu de masquer les erreurs.
Staging obligatoire pour les changements risqués
Tout ce qui touche:
- defer/async
- désenregistrement de scripts du builder
- nettoyage DB
… doit être testé sur une copie. J’ai vu des sites perdre leur header parce qu’un wp_dequeue_script mal placé supprimait un script de navigation.
Ressources
- Query Monitor (wordpress.org)
- Debugging in WordPress (developer.wordpress.org)
- update_meta_cache() (developer.wordpress.org)
- wp_remote_get() (developer.wordpress.org)
- Filtre script_loader_tag (developer.wordpress.org)
- Transients API (developer.wordpress.org)
- WordPress Core Trac (core.trac.wordpress.org)
- WordPress Develop (GitHub)
- OPcache configuration (php.net)
FAQ
Query Monitor ralentit-il mon site ?
Un peu, oui, parce qu’il collecte et affiche des données. Mais pour un admin en phase de diagnostic, c’est négligeable face au temps que vous gagnez à trouver la vraie cause. Je le désactive généralement après correction.
Dois-je activer WP_DEBUG sur un site en production ?
Oui, temporairement, mais avec WP_DEBUG_DISPLAY à false pour ne rien afficher aux visiteurs. Gardez le log le temps d’identifier le plugin/thème fautif.
Où coller le code de cet article si j’utilise Divi 5 / Elementor / Avada ?
Pour tout ce qui est diagnostic (profiler, logs), utilisez un mu-plugin. Pour les optimisations de rendu (shortcodes, fragments), un plugin custom est plus propre qu’un snippet dans le thème. Ensuite, insérez via un module “Code/HTML/Shortcode” du builder.
Pourquoi mon site est rapide en navigation privée mais lent quand je suis connecté ?
Parce que connecté = pas (ou moins) de cache page, et plus de travail (barre d’admin, préchargements, requêtes de personnalisation). Testez les deux. Si la lenteur n’existe que connecté, cherchez côté hooks admin, plugins d’édition, ou Query Monitor sur des pages wp-admin.
Que signifie “requêtes dupliquées” dans Query Monitor ?
Ce sont des requêtes SQL identiques rejouées plusieurs fois. Causes classiques: N+1, appels répétitifs à get_option() sur des options non autoload, ou boucles qui recalculent la même chose. Solution: cache (objet/transient), préchargement (update_meta_cache), et refactor.
J’ai activé WP_DEBUG et je vois des centaines de warnings. Ça ralentit vraiment ?
Oui, surtout si ça écrit en boucle dans debug.log. Mais le vrai problème est généralement que le plugin/thème fait des opérations inutiles. Corrigez la source. Si c’est un plugin tiers, mettez-le à jour ou remplacez-le.
Est-ce que “minifier” CSS/JS suffit pour passer les Core Web Vitals ?
Rarement. La minification aide un peu, mais les gros gains viennent de: supprimer des scripts inutiles, retarder le non critique (defer), réduire les modules/animations, optimiser l’image LCP et la police critique.
Comment savoir si mon cache page fonctionne vraiment ?
Mesurez le TTFB avec curl et testez deux fois. La deuxième requête doit être nettement plus rapide. Vous pouvez aussi vérifier des en-têtes (selon votre solution de cache/CDN), mais ça dépend de l’hébergeur.
Je ne peux pas installer Query Monitor (politique entreprise). Je fais comment ?
Utilisez le mu-plugin de profiler et les logs: mesurez le temps total, puis encadrez les zones suspectes (shortcodes, templates, hooks). Combinez avec WP-CLI (autoload, cron). Vous irez moins vite, mais vous trouverez quand même.
J’ai mis du code et mon site affiche une page blanche. Que faire ?
Ça arrive souvent avec un point-virgule manquant. Désactivez le mu-plugin en renommant le fichier via FTP/SFTP, ou renommez le dossier du plugin fautif. Ensuite, regardez wp-content/debug.log pour l’erreur fatale.