Le problème de performance
Si vous voyez dans Lighthouse un Largest Contentful Paint qui explose sur mobile alors que votre contenu est simple, il y a souvent un coupable banal : vous chargez des scripts et des styles partout, même sur les pages qui n’en ont pas besoin.
Le problème vient de l’accumulation. Un slider utilisé sur une seule page injecte son JS sur tout le site. Un formulaire présent sur “Contact” charge sa librairie sur l’accueil. Et sur des sites avec Elementor, Divi 5 ou Avada, j’ai souvent vu des bundles “globaux” (animations, carrousels, popups) chargés même sur des pages statiques qui n’en utilisent aucun module.
Impact concret :
- Core Web Vitals : plus de JS = plus de travail sur le thread principal => TBT et INP se dégradent.
- SEO : un LCP plus lent et un INP mauvais se traduisent par une baisse de visibilité, surtout sur mobile.
- UX : pages qui “saccadent”, clics retardés, menus qui répondent mal.
À la fin, vous saurez :
- identifier précisément quel handle WordPress charge, sur quelles pages, et combien ça coûte,
- décharger proprement des scripts/styles sans casser l’éditeur, le cache ou les pages builder,
- recharger uniquement là où il faut, et au bon moment (
defer/async, modules, preload ciblé).
Résumé rapide
- Mesurez d’abord : enregistrez les handles réellement chargés et leur coût (front uniquement).
- Déchargez avec
wp_dequeue_script()/wp_deregister_script()etwp_dequeue_style()au bon hook (wp_enqueue_scripts, priorité élevée). - Rechargez uniquement sur les pages concernées via conditions (template, ID, slug, type de contenu, présence de shortcode, présence de bloc, etc.).
- Ajoutez
defer/asyncviascript_loader_tag(pas en “bricolant” le HTML). - Pour Elementor/Divi/Avada : conditionnez par présence réelle de widget/module (ou fallback par détection de contenu) pour éviter les faux positifs.
- Vérifiez après chaque changement : INP/TBT, waterfall, et test “page builder” (éditeur + front).
Diagnostic avec du code
1) Activer un logging propre (sans tout casser en prod)
Sur WordPress 6.9.4 et PHP 8.1+, je préfère logguer vers un fichier, pas afficher. Ajoutez ceci dans wp-config.php (temporairement).
/**
* Diagnostic performance (temporaire)
* WordPress 6.9.4+ / PHP 8.1+
*/
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
/**
* Pratique pour comparer des mesures (ne pas laisser si vous n'en avez pas l'usage)
*/
define('SAVEQUERIES', true);
Risque : SAVEQUERIES augmente la mémoire et ralentit. Je l’active uniquement pour diagnostiquer, puis je le retire.
2) Logger les scripts/styles réellement chargés (front)
Le but : obtenir une liste exploitable des handles et de leurs src, page par page. Placez ce snippet dans un mini-plugin (recommandé) plutôt que dans functions.php, pour éviter qu’un changement de thème le supprime.
<?php
/**
* Plugin Name: Perf - Asset Map (diagnostic)
* Description: Log les scripts et styles en file d'attente sur le front.
* Version: 0.1.0
*/
if (!defined('ABSPATH')) {
exit;
}
add_action('wp_enqueue_scripts', function () {
// Ne logguez pas sur l'admin, ni sur les requêtes AJAX/REST
if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
return;
}
// Éviter de spammer les logs pour les visiteurs
if (!current_user_can('manage_options')) {
return;
}
global $wp_scripts, $wp_styles;
$url = home_url(add_query_arg([]));
$screen = is_singular() ? ('singular:' . get_post_type() . ':' . get_queried_object_id()) : 'non-singular';
$scripts = [];
if ($wp_scripts instanceof WP_Scripts) {
foreach ((array) $wp_scripts->queue as $handle) {
$src = isset($wp_scripts->registered[$handle]) ? (string) $wp_scripts->registered[$handle]->src : '';
$scripts[] = $handle . ' | ' . $src;
}
}
$styles = [];
if ($wp_styles instanceof WP_Styles) {
foreach ((array) $wp_styles->queue as $handle) {
$src = isset($wp_styles->registered[$handle]) ? (string) $wp_styles->registered[$handle]->src : '';
$styles[] = $handle . ' | ' . $src;
}
}
error_log('=== ASSET MAP ===');
error_log('URL: ' . $url);
error_log('Contexte: ' . $screen);
error_log('Scripts (' . count($scripts) . '): ' . implode(' || ', $scripts));
error_log('Styles (' . count($styles) . '): ' . implode(' || ', $styles));
error_log('=== /ASSET MAP ===');
}, 999);
Ouvrez wp-content/debug.log et comparez “Accueil”, “Article”, “Contact”, “Page builder lourde”. Vous allez généralement repérer 3 catégories :
- assets “globaux” légitimes (navigation, thème, blocs),
- assets “globaux” inutiles (slider, lightbox, animations),
- assets chargés par un plugin qui ne devrait pas (formulaire, anti-spam, tracking).
3) Mesurer le temps et la pression SQL (sans outil externe)
Query Monitor reste l’outil n°1 (et il est fiable), mais j’aime bien avoir un micro-timer reproductible dans les logs pour confirmer un gain.
add_action('shutdown', function () {
if (is_admin() || !current_user_can('manage_options')) {
return;
}
$time_s = timer_stop(0, 5); // Temps total WordPress en secondes (string)
$mem_mb = round(memory_get_peak_usage(true) / 1024 / 1024, 1);
$queries = 0;
if (defined('SAVEQUERIES') && SAVEQUERIES && isset($GLOBALS['wpdb'])) {
$queries = (int) $GLOBALS['wpdb']->num_queries;
}
error_log(sprintf(
'PERF shutdown | url=%s | time=%ss | peak_mem=%sMB | queries=%d',
home_url(add_query_arg([])),
$time_s,
$mem_mb,
$queries
));
}, 9999);
Ce n’est pas un “TTFB” réel (il inclut le rendu), mais c’est un indicateur stable pour comparer avant/après sur la même page, mêmes conditions.
4) WP-CLI : vérifier rapidement et vider les caches
Quelques commandes que j’utilise en boucle pendant ce type de chantier :
# Vérifier versions
wp core version
wp plugin list --status=active
wp theme list
# Vider caches (si un plugin de cache expose wp-cli, sinon au moins object cache)
wp cache flush
# Recompiler les permaliens si un snippet touche les règles (rare ici, mais utile en dépannage)
wp rewrite flush --hard
5) Slow query log (si vous suspectez un plugin qui ralentit AVANT même les assets)
Le conditional loading aide surtout le front (JS/CSS). Si votre TTFB est déjà mauvais, activez le slow query log côté MySQL/MariaDB (ou utilisez la partie “Queries” de Query Monitor). Côté PHP-FPM, vous pouvez aussi activer le slowlog, mais ça dépasse souvent l’hébergement mutualisé.
Sources officielles utiles :
- wp_enqueue_script()
- wp_dequeue_script()
- Hook wp_enqueue_scripts
- Filtre script_loader_tag
- Performance (Advanced Administration)
Étape 1 : cartographier et décharger les assets inutiles
La plupart des gens “optimisent” en devinant. Ça finit en régression : un script disparaît sur une page critique, ou pire, dans l’éditeur.
Je procède toujours en deux temps :
- Cartographier : obtenir les handles exacts (section diagnostic).
- Décharger : retirer uniquement ce qui est objectivement inutile, avec un garde-fou (ne jamais toucher admin/REST/AJAX).
Exemple réel : un plugin de formulaire charge partout
Cas courant : un plugin de formulaire (ou anti-spam) enfile ses assets sur tout le site, alors que vous n’avez un formulaire que sur “Contact”.
AVANT (lent) : enqueue global
Ce genre de code est fréquent dans des petits plugins ou des snippets :
add_action('wp_enqueue_scripts', function () {
// Mauvais : chargé sur toutes les pages
wp_enqueue_style('mon-form', plugins_url('form.css', __FILE__), [], '1.0');
wp_enqueue_script('mon-form', plugins_url('form.js', __FILE__), ['jquery'], '1.0', true);
});
APRÈS (optimisé) : dequeue global + recharge conditionnelle
Si vous ne contrôlez pas le plugin, vous pouvez décharger ses handles sur la majorité des pages, puis les réactiver sur la page cible. Le point clé : agir après le plugin (priorité élevée).
add_action('wp_enqueue_scripts', function () {
if (is_admin() || wp_doing_ajax() || (defined('REST_REQUEST') && REST_REQUEST)) {
return;
}
// Exemple : handles repérés via debug.log / Query Monitor
$style_handle = 'mon-form';
$script_handle = 'mon-form';
// Garder uniquement sur la page "Contact" (ID 42 ici)
$keep = is_page(42);
if (!$keep) {
// Retire de la file d'attente (si déjà enqueued)
wp_dequeue_style($style_handle);
wp_dequeue_script($script_handle);
// Optionnel : empêcher la réinscription plus tard dans la requête
wp_deregister_style($style_handle);
wp_deregister_script($script_handle);
}
}, 999);
Pourquoi c’est plus rapide : moins de CSS/JS à télécharger et à parser sur toutes les pages. Sur mobile, c’est souvent le parsing JS qui coûte cher (TBT/INP).
Mesure d’impact (simple et reproductible)
Avant le changement, notez :
- le nombre de scripts/styles dans votre log “ASSET MAP”,
- le temps
PERF shutdownsur 3 chargements (moyenne), - dans Lighthouse : JS total, TBT, INP (field data si disponible).
Après le changement, refaites exactement les mêmes tests, en vidant cache page + navigateur. Sur des sites “plugin-heavy”, je vois souvent une baisse de 50–300 KB de JS par page et un TBT qui descend de 100–400 ms sur mobile, sans toucher au serveur.
Étape 2 : charger un script uniquement là où il sert
Décharger est utile, mais le vrai “clean” consiste à conditionner l’enqueue dès le départ. Si vous maîtrisez votre thème enfant ou votre plugin, faites-le à la source.
Conditionner par type de page (is_page, is_singular, template)
Exemple : une librairie de lightbox utilisée uniquement sur les articles (pas sur les pages).
AVANT : partout
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('lightbox', get_stylesheet_directory_uri() . '/assets/lightbox.js', [], '2.3.0', true);
wp_enqueue_style('lightbox', get_stylesheet_directory_uri() . '/assets/lightbox.css', [], '2.3.0');
});
APRÈS : uniquement sur les articles
add_action('wp_enqueue_scripts', function () {
if (!is_singular('post')) {
return;
}
wp_enqueue_script(
'lightbox',
get_stylesheet_directory_uri() . '/assets/lightbox.js',
[],
'2.3.0',
['in_footer' => true] // API moderne : évite le bool ambigu
);
wp_enqueue_style(
'lightbox',
get_stylesheet_directory_uri() . '/assets/lightbox.css',
[],
'2.3.0'
);
}, 20);
J’insiste sur ['in_footer' => true] : c’est plus lisible, et ça évite les erreurs de “paramètre 5” quand quelqu’un refactorise.
Conditionner par présence de shortcode (sans faux positifs)
Beaucoup font has_shortcode(get_the_content(), 'gallery') trop tôt, hors boucle, et ça ne marche pas (ou ça marche “par hasard”). Je fais plutôt :
- sur les pages singulières, je récupère le post global,
- je teste
post_contentproprement.
add_action('wp_enqueue_scripts', function () {
if (!is_singular()) {
return;
}
$post = get_queried_object();
if (!$post instanceof WP_Post) {
return;
}
// Exemple : script nécessaire uniquement si le shortcode [mon_slider] est présent
if (!has_shortcode($post->post_content, 'mon_slider')) {
return;
}
wp_enqueue_script(
'mon-slider',
get_stylesheet_directory_uri() . '/assets/mon-slider.js',
[],
'1.4.0',
['in_footer' => true]
);
}, 20);
Edge case : si votre page builder stocke le contenu en JSON/shortcodes imbriqués, ce test peut rater. Dans ce cas, passez à la détection “builder” (Étape 4) ou à un flag explicite (meta, option, bloc).
Conditionner par présence de bloc (Gutenberg)
Sur WordPress 6.9.4, le plus robuste est d’utiliser has_block() sur le contenu du post.
add_action('wp_enqueue_scripts', function () {
if (!is_singular()) {
return;
}
$post = get_queried_object();
if (!$post instanceof WP_Post) {
return;
}
// Exemple : script uniquement si le bloc "core/gallery" est utilisé
if (!has_block('core/gallery', $post)) {
return;
}
wp_enqueue_script(
'gallery-enhancements',
get_stylesheet_directory_uri() . '/assets/gallery.js',
[],
'1.0.0',
['in_footer' => true]
);
}, 20);
Dans mon expérience, c’est une des optimisations les plus “safe” sur des sites éditoriaux modernes : pas besoin de maintenir une liste de pages, le contenu pilote.
Étape 3 : charger au bon moment (defer/async, modules et preload)
Conditional loading réduit le volume. Ensuite, vous gagnez encore en évitant de bloquer le rendu.
Ajouter defer/async proprement via script_loader_tag
Ne faites pas de regex sur le HTML final. Utilisez le filtre WordPress prévu.
add_filter('script_loader_tag', function ($tag, $handle, $src) {
// Liste blanche : ne touchez pas aux scripts critiques ou dépendants de l'ordre sans audit
$defer_handles = [
'gallery-enhancements',
'mon-slider',
];
if (!in_array($handle, $defer_handles, true)) {
return $tag;
}
// Ne pas modifier si déjà "defer" (évite doublons)
if (str_contains($tag, ' defer')) {
return $tag;
}
// Ajoute defer avant src (HTML valide)
return str_replace(' src=', ' defer src=', $tag);
}, 10, 3);
Anti-pattern fréquent : mettre defer sur un script qui doit s’exécuter avant un autre (ex : dépendances non déclarées, inline script qui attend une variable). Résultat : erreurs JS intermittentes, souvent invisibles sur desktop mais présentes sur mobile.
Charger en module (type="module") quand vous contrôlez le script
Si votre JS est moderne et bundlé pour les navigateurs actuels, vous pouvez le servir en module. Attention : ça change la portée et la façon de gérer les imports.
add_filter('script_loader_tag', function ($tag, $handle, $src) {
if ($handle !== 'gallery-enhancements') {
return $tag;
}
// Remplace type='text/javascript' implicite par type="module"
// Note : WordPress génère généralement <script src="..."></script> sans attribut type.
$tag = str_replace('<script ', '<script type="module" ', $tag);
// Les modules sont defer par défaut, inutile d'ajouter defer
return $tag;
}, 10, 3);
Preload ciblé (uniquement si vous êtes sûr)
Précharger “tout” est une erreur classique. Préchargez uniquement un fichier critique au-dessus de la ligne de flottaison, et seulement sur les pages concernées.
add_action('wp_head', function () {
if (!is_front_page()) {
return;
}
// Exemple : hero.css uniquement sur la home
$href = get_stylesheet_directory_uri() . '/assets/hero.css';
echo '<link rel="preload" href="' . esc_url($href) . '" as="style">' . "n";
}, 1);
Note perf : si votre cache/minification regroupe les CSS, ce preload peut devenir inutile ou pointer vers une URL qui change. Vérifiez votre pipeline.
Étape 4 : conditional loading pour blocks, Elementor, Divi 5 et Avada
Sur les page builders, le piège est la détection. Vous voulez charger un asset uniquement si un module/widget est présent, mais le contenu est parfois stocké en meta (pas dans post_content).
Gutenberg / blocs : le cas le plus simple
Si vous êtes 100% blocs, restez sur has_block() (Étape 2). C’est rapide et fiable.
Elementor : conditionner quand Elementor rend la page
Elementor a des APIs internes, mais elles évoluent. En 2026, je privilégie une approche robuste :
- détecter si la page est construite avec Elementor via meta,
- puis charger des assets spécifiques uniquement dans ce cas.
add_action('wp_enqueue_scripts', function () {
if (!is_singular()) {
return;
}
$post_id = get_queried_object_id();
if (!$post_id) {
return;
}
// Elementor stocke généralement un flag dans le meta _elementor_edit_mode
$edit_mode = get_post_meta($post_id, '_elementor_edit_mode', true);
if (empty($edit_mode)) {
return;
}
// Exemple : script d'animation uniquement pour pages Elementor
wp_enqueue_script(
'site-animations',
get_stylesheet_directory_uri() . '/assets/animations.js',
[],
'1.2.0',
['in_footer' => true]
);
}, 20);
Observation : j’ai déjà vu des sites où _elementor_edit_mode est vide alors que la page est Elementor (migration, import). Dans ce cas, vous pouvez aussi tester la présence de _elementor_data :
function bpcab_is_elementor_page(int $post_id): bool {
$data = get_post_meta($post_id, '_elementor_data', true);
return !empty($data);
}
Divi 5 : éviter le “tout global” et conditionner par shortcode/module
Divi a historiquement utilisé des shortcodes dans le contenu. Divi 5 modernise beaucoup, mais sur le terrain je vois encore énormément de pages avec des signatures reconnaissables dans post_content (ou via meta selon configuration).
Approche pragmatique : conditionner par présence d’un motif (shortcode/module) et prévoir un fallback par template.
add_action('wp_enqueue_scripts', function () {
if (!is_singular('page')) {
return;
}
$post = get_queried_object();
if (!$post instanceof WP_Post) {
return;
}
// Détection simple (à adapter selon votre contenu Divi)
$looks_like_divi = str_contains($post->post_content, '[et_pb_') || str_contains($post->post_content, 'et_pb_section');
// Fallback : template spécifique "page-landing.php"
$looks_like_landing = is_page_template('page-landing.php');
if (!$looks_like_divi && !$looks_like_landing) {
return;
}
wp_enqueue_script(
'landing-effects',
get_stylesheet_directory_uri() . '/assets/landing-effects.js',
[],
'1.0.0',
['in_footer' => true]
);
}, 20);
Pourquoi je le fais comme ça : vouloir détecter “le module exact” de Divi côté PHP est vite fragile. Ici, vous gagnez déjà beaucoup en évitant de charger sur les articles, catégories, pages simples.
Avada / Fusion Builder : conditionner par contenu et par post meta
Avada stocke souvent des shortcodes Fusion dans le contenu. Même logique : détection par signature, puis enqueue.
add_action('wp_enqueue_scripts', function () {
if (!is_singular()) {
return;
}
$post = get_queried_object();
if (!$post instanceof WP_Post) {
return;
}
// Fusion shortcodes (exemples fréquents)
$uses_fusion = str_contains($post->post_content, '[fusion_') || str_contains($post->post_content, 'fusion_builder');
if (!$uses_fusion) {
return;
}
wp_enqueue_style(
'fusions-helpers',
get_stylesheet_directory_uri() . '/assets/fusion-helpers.css',
[],
'1.0.0'
);
}, 20);
Cas “widget présent mais rendu via AJAX”
Si un module charge du contenu après coup (popups, filtres produits, recherche instantanée), la détection par contenu peut échouer. Dans ce cas, le plus propre est :
- poser un flag explicite (meta post, option, champ ACF),
- et enqueuer selon ce flag.
function bpcab_page_needs_filters(int $post_id): bool {
return (bool) get_post_meta($post_id, '_needs_filters_js', true);
}
add_action('wp_enqueue_scripts', function () {
if (!is_singular()) {
return;
}
$post_id = get_queried_object_id();
if (!$post_id || !bpcab_page_needs_filters($post_id)) {
return;
}
wp_enqueue_script(
'filters',
get_stylesheet_directory_uri() . '/assets/filters.js',
[],
'1.0.0',
['in_footer' => true]
);
}, 20);
Ce pattern est très stable en équipe : pas besoin d’“inspecter” le builder, un éditeur coche une case, et la page charge ce qu’il faut.
Étape 5 : réduire le coût CSS et fonts par page
On parle souvent JS, mais le CSS global est tout aussi toxique sur mobile : il retarde le rendu, et il augmente le coût de recalcul de style.
Découper un CSS par gabarit (home, article, page)
Exemple simple : au lieu d’un theme.css énorme, vous gardez un “core” et vous ajoutez des couches conditionnelles.
add_action('wp_enqueue_scripts', function () {
// CSS de base partout
wp_enqueue_style(
'theme-core',
get_stylesheet_directory_uri() . '/assets/css/core.css',
[],
'1.0.0'
);
// CSS spécifique home
if (is_front_page()) {
wp_enqueue_style(
'theme-home',
get_stylesheet_directory_uri() . '/assets/css/home.css',
['theme-core'],
'1.0.0'
);
}
// CSS spécifique articles
if (is_singular('post')) {
wp_enqueue_style(
'theme-post',
get_stylesheet_directory_uri() . '/assets/css/post.css',
['theme-core'],
'1.0.0'
);
}
}, 20);
Mesure : regardez la “Coverage” dans Chrome DevTools (ou les rapports de votre bundler) pour vérifier que vous réduisez vraiment le CSS inutilisé. J’ai vu des gains très nets sur LCP juste en sortant le CSS “landing” du global.
Fonts : ne chargez pas 6 graisses partout
Sans entrer dans un débat de design : sur beaucoup de blogs, 2 graisses suffisent sur 90% des pages. Le reste peut être conditionnel (landing pages marketing) ou supprimé.
Si vous enfilez des fonts via @import dans CSS, vous perdez du contrôle. Préférez un enqueue et conditionnez.
add_action('wp_enqueue_scripts', function () {
// Exemple : font marketing uniquement sur pages de vente
if (!is_page_template('page-sales.php')) {
return;
}
wp_enqueue_style(
'marketing-fonts',
'https://example-cdn.invalid/fonts/marketing.css',
[],
'1.0.0'
);
}, 20);
Note sécurité : charger des ressources externes ajoute un risque (disponibilité, confidentialité). Si vous le pouvez, self-hostez et servez via votre CDN.
Configuration serveur
Le conditional loading agit surtout sur le poids et l’exécution front. Mais si votre serveur ne met pas en cache correctement les assets statiques, vous perdez une partie du gain.
.htaccess (Apache) : cache long pour assets versionnés
Si vos fichiers ont une version dans l’URL (WordPress ajoute ?ver=), vous pouvez quand même mettre un cache long, mais idéalement vous versionnez par nom de fichier (build). À défaut, voici une base raisonnable.
# .htaccess - cache statique (à placer dans le vhost ou le .htaccess racine si autorisé)
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 30 days"
ExpiresByType application/javascript "access plus 30 days"
ExpiresByType text/javascript "access plus 30 days"
ExpiresByType image/svg+xml "access plus 30 days"
ExpiresByType image/png "access plus 30 days"
ExpiresByType image/jpeg "access plus 30 days"
ExpiresByType font/woff2 "access plus 180 days"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch ".(css|js|png|jpg|jpeg|svg|woff2)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
</IfModule>
Piège : si votre pipeline ne change jamais les URLs (pas de hash, pas de version), immutable peut bloquer des mises à jour côté visiteurs. Testez sur un navigateur “froid”.
wp-config.php : désactiver la concaténation admin uniquement si vous debuggez
Quand vous chassez un conflit d’assets, désactiver la concaténation admin peut aider (mais ne laissez pas ça en permanence).
// Uniquement pour debug en admin : facilite l'identification des fichiers
define('CONCATENATE_SCRIPTS', false);
php.ini / PHP-FPM : OPcache (si vous avez la main)
OPcache n’accélère pas directement le JS/CSS, mais il réduit le coût PHP (TTFB). Si vous faites beaucoup de tests, vous voulez une base serveur saine.
; php.ini (exemple)
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
Source : PHP OPcache configuration
Vérification des résultats
Après chaque série de changements, je vérifie en trois couches : logs WordPress, navigateur, et métriques.
1) Vérifier que les handles ont disparu (debug.log)
Rechargez une page qui ne devrait pas charger le script. Dans debug.log, cherchez le handle. S’il est encore là :
- vous n’êtes pas au bon hook,
- ou votre priorité est trop basse,
- ou un autre plugin le ré-enqueue après vous.
2) Vérifier qu’il n’y a pas d’erreurs JS (console)
Un conditional loading mal fait se voit souvent via :
Uncaught ReferenceError(lib absente),Cannot read properties of undefined(ordre d’exécution cassé),- erreurs uniquement sur certaines pages (c’est le pire, car ça passe en QA).
3) Mesurer (Lighthouse / WebPageTest / PageSpeed)
Pour rester “code-centric”, voici un check-list de mesures :
- JS total téléchargé sur la page (doit baisser sur les pages non concernées)
- TBT (souvent baisse si vous supprimez des libs)
- INP (si vous retirez des listeners/animations globales)
- LCP (si vous réduisez CSS bloquant)
Je garde aussi un repère simple : le log PERF shutdown sur 5 chargements, moyenne. Ce n’est pas parfait, mais ça détecte vite une régression.
Si les performances ne s’améliorent pas
Quand le conditional loading ne bouge presque rien, c’est généralement que votre goulot n’est pas là. Voilà mon plan de triage.
1) TTFB déjà mauvais : cherchez PHP/SQL avant les assets
- désactivez temporairement les plugins non essentiels (staging),
- utilisez Query Monitor pour repérer des requêtes lentes,
- regardez si un cache objet est actif (Redis/Memcached) et fonctionnel.
2) Un seul script “monstre” reste global
Souvent : un bundle d’animations, un framework, ou un “core” de builder. Là, vous avez deux options réalistes :
- le conditionner au maximum (pages builder uniquement),
- le remplacer par du JS plus léger (mais c’est un chantier).
3) Cache/minification vous masque les changements
J’ai souvent vu : vous dequeue un handle, mais un plugin de perf sert un fichier concaténé qui inclut encore le code. Dans ce cas :
- désactivez temporairement la minification/concat,
- refaites la cartographie,
- réactivez ensuite et vérifiez que le pipeline respecte vos conditions.
4) Mobile uniquement : CPU, pas réseau
Si le poids baisse mais INP/TBT ne bouge pas, c’est que le JS restant est coûteux à exécuter. Cherchez :
- des listeners globaux (scroll/resize) non throttlés,
- des animations lourdes,
- des carrousels initialisés même quand invisibles.
Pièges et erreurs courantes
Erreurs que je vois tout le temps
- Copier le code au mauvais endroit : snippet dans un plugin de cache/minify qui le réécrit, ou dans un thème parent qui sera mis à jour.
- Hook inadapté : utiliser
initau lieu dewp_enqueue_scripts, puis se demander pourquoi ça ne marche pas. - Priorité trop basse : votre
wp_dequeue_script()s’exécute avant l’enqueue du plugin, donc n’a aucun effet. - Oublier de vider les caches : cache page, cache navigateur, CDN.
- Tester en production sans sauvegarde/staging : une page builder peut casser silencieusement.
- Code d’un vieux tutoriel : paramètres d’enqueue mal utilisés, ou filtres sur des hooks obsolètes.
Tableau de diagnostic
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| Le script est toujours chargé malgré le dequeue | Priorité trop basse ou mauvais hook | Loggez l’ordre via error_log et comparez les priorités |
Déplacez sur wp_enqueue_scripts priorité 999 (ou plus) et évitez init |
| Une page casse (JS error) après optimisation | Dépendance non déclarée, ordre d’exécution cassé par defer |
Console : ReferenceError, waterfall JS |
Déclarez les dépendances dans wp_enqueue_script, retirez defer du handle concerné |
| Ça marche sur desktop, pas sur mobile | Conditions trop strictes, contenu différent (builder, cache) | Comparez HTML rendu + assets map sur mobile/desktop | Conditionnez par type de contenu ou flag meta plutôt que par détection fragile |
| Les changements n’ont aucun effet | Minification/concat sert un bundle unique | Désactivez temporairement la minification, re-testez | Configurez le plugin de perf pour respecter les exclusions par handle ou par URL |
| Le CSS manque sur certaines pages | Style enqueued conditionnel mais besoin global (composant partagé) | Inspecter : règles manquantes, classes non stylées | Gardez un core.css global et conditionnez uniquement les “couches” |
| Éditeur Elementor/Divi/Avada instable | Vous dequeue des assets côté admin/preview | Testez l’éditeur + preview, vérifiez les conditions is_admin() |
Ajoutez des garde-fous : ne rien toucher sur admin/REST/AJAX, et testez les modes preview |
Conseils de maintenance
- Gardez une liste blanche des handles modifiés (dequeue/defer). Quand un plugin se met à jour, vous savez quoi revalider.
- Ajoutez des logs temporaires lors des mises à jour majeures (builder, thème, plugin de perf), puis retirez-les.
- Documentez les conditions : “tel script seulement sur page X ou si bloc Y”. Dans 6 mois, vous aurez oublié pourquoi.
- Testez toujours : une page simple, une page builder lourde, une page formulaire, et l’éditeur.
- Automatisez si possible : un test Lighthouse CI sur 3–5 URLs critiques (même en interne) détecte les régressions.
Ressources
- wp_enqueue_script() (Developer Reference)
- wp_enqueue_style() (Developer Reference)
- wp_dequeue_script() (Developer Reference)
- wp_deregister_script() (Developer Reference)
- script_loader_tag (filtre officiel)
- has_block() (détection de blocs)
- Query Monitor (plugin)
- WordPress Core (mirror GitHub)
- WordPress Core Trac
- memory_get_peak_usage() (PHP)
FAQ
Dois-je utiliser wp_dequeue_script() ou wp_deregister_script() ?
wp_dequeue_script() retire le script de la file d’attente pour cette requête. wp_deregister_script() supprime aussi son enregistrement, ce qui évite qu’il soit re-enqueue plus tard. En pratique, je fais souvent les deux quand je veux être sûr qu’un plugin ne le réinjecte pas.
Pourquoi mon dequeue ne fonctionne pas alors que le handle est correct ?
Dans 80% des cas : votre code s’exécute trop tôt. Passez votre callback sur wp_enqueue_scripts avec une priorité élevée (ex : 999) pour passer après les plugins.
Est-ce que je peux conditionner avec is_page('contact') ?
Oui, mais attention aux traductions (WPML/Polylang) et aux changements de slug. Un ID de page est plus stable. Sur des sites multilingues, je préfère un template dédié ou un flag meta.
has_shortcode() est fiable avec Elementor/Divi/Avada ?
Pas toujours. Si le builder stocke la structure en meta (JSON), post_content peut ne pas contenir le shortcode attendu. Utilisez une détection par meta (ex : _elementor_data) ou un flag explicite.
Puis-je mettre defer sur jQuery pour accélérer ?
Je l’évite. Beaucoup de scripts (thème, plugins) supposent que jQuery est disponible immédiatement. Si vous tenez à le faire, auditez toutes les dépendances et testez sur mobile, pages builder, et formulaires.
Comment éviter de casser l’éditeur (Gutenberg / Elementor) ?
Ajoutez des garde-fous : ne modifiez rien sur is_admin(), wp_doing_ajax(), ni sur les requêtes REST. Et testez les modes preview des builders, qui peuvent utiliser le front avec des paramètres spécifiques.
Mon plugin de cache regroupe tout en un fichier. Le conditional loading sert encore ?
Oui, mais vous devez configurer le plugin de cache/minification pour respecter les exclusions par handle ou par URL. Sinon, vous aurez l’impression que rien ne change, car le bundle reste identique.
Quelle est la meilleure condition : template, ID, bloc, shortcode ?
Pour un site éditorial : bloc (ou contenu) est souvent le plus robuste. Pour des pages marketing : template ou ID. Pour des cas dynamiques (AJAX) : flag meta.
Dois-je faire ça dans functions.php ?
Je le fais rarement. Un mini-plugin “site-perf” est plus durable, versionnable, et indépendant du thème. C’est aussi plus simple à désactiver si vous devez dépanner vite.
Comment prouver le gain sans outil externe ?
Logguez les handles (avant/après), puis logguez timer_stop(), mémoire, et nombre de requêtes. Ce n’est pas un benchmark parfait, mais ça détecte des gains et surtout des régressions rapidement.