Un client éditeur de recettes de cuisine se plaignait d’un LCP mobile qui dépassait régulièrement 3,8 secondes sur ses pages de recettes, alors que le contenu principal (une photo du plat et son titre) tenait en quelques kilo-octets. Le coupable n’était pas l’image, déjà optimisée, mais une bonne dizaine de scripts chargés avant elle, qui retardaient le moment où le navigateur pouvait enfin peindre l’élément principal à l’écran.
Voici la recette suivie pour ce thème WordPress maison, du diagnostic à la correction, avec les extraits de code effectivement utilisés.
Le problème : du JavaScript qui devance le contenu
Sur ce thème, l’ordre de chargement dans le <head> plaçait plusieurs scripts avant le premier octet de contenu visible : un script de mesure d’audience, une bibliothèque de carrousel utilisée uniquement en bas de page, et le script d’un plugin d’avis clients. Aucun de ces trois éléments n’était nécessaire pour afficher la photo et le titre de la recette, pourtant tous bloquaient le rendu jusqu’à leur exécution complète.
Le snippet commenté
La première correction a consisté à revoir l’enregistrement des scripts via wp_enqueue_script() pour leur ajouter systématiquement une stratégie de chargement adaptée, plutôt que de laisser WordPress les insérer en synchrone par défaut :
function wpmoderne_scripts_differes() {
// Script d'audience : non critique pour l'affichage, en différé
wp_enqueue_script(
'audience-tracker',
get_template_directory_uri() . '/js/audience.js',
array(),
'1.4.0',
array( 'strategy' => 'defer' )
);
// Carrousel de recettes similaires : chargé en bas de page, différé aussi
wp_enqueue_script(
'carrousel-similaires',
get_template_directory_uri() . '/js/carrousel.js',
array(),
'2.1.0',
array( 'strategy' => 'defer', 'in_footer' => true )
);
}
add_action( 'wp_enqueue_scripts', 'wpmoderne_scripts_differes' );

Le paramètre strategy de wp_enqueue_script(), disponible depuis WordPress 6.3, permet de demander defer ou async directement dans l’appel PHP, sans passer par un filtre supplémentaire sur la balise générée. C’est un gain de lisibilité appréciable par rapport aux anciennes méthodes qui manipulaient la chaîne de caractères de la balise <script>.
defer et async ne sont pas interchangeables
Sur ce projet, l’erreur initiale de l’équipe avait été de mettre async partout, en pensant que « plus c’est asynchrone, mieux c’est ». Or async exécute le script dès qu’il est téléchargé, sans attendre la fin du parsing HTML, ce qui peut encore interrompre le rendu à un moment imprévisible. defer, à l’inverse, garantit une exécution après le parsing complet du document, dans l’ordre de déclaration : c’est le choix le plus sûr pour tout ce qui n’a pas besoin de s’exécuter immédiatement.
defer: pour les scripts qui manipulent le DOM une fois qu’il est complet (carrousels, compteurs, widgets d’avis).async: pour les scripts totalement indépendants du DOM, comme certains scripts de mesure qui n’attendent aucun élément.- Aucune stratégie (comportement par défaut) : réservé au strict minimum nécessaire à l’affichage initial.
Le script d’avis clients, un cas particulier
Le script du plugin d’avis clients posait un problème différent : il n’était pas seulement bloquant, il était surtout inutile sur la majorité des pages de recettes qui n’affichaient pas encore d’avis. Plutôt que de le différer, la solution la plus efficace a été de ne le charger que sur les pages qui en avaient réellement besoin, via une condition sur is_singular() et la présence du bloc concerné dans le contenu :
if ( is_singular( 'recette' ) && has_block( 'avis-plugin/etoiles' ) ) {
wp_enqueue_script( 'avis-clients', $chemin_script, array(), '3.0.2', array( 'strategy' => 'defer' ) );
}
Résultats mesurés
| Page testée | LCP mobile avant | LCP mobile après |
|---|---|---|
| Recette de tarte aux pommes | 3,8 s | 2,9 s |
| Page catégorie « desserts » | 4,1 s | 3,2 s |
Le gain de 900 millisecondes en moyenne ne vient pas d’une seule bascule mais de l’accumulation de trois décisions modestes : différer ce qui peut l’être, choisir la bonne stratégie entre defer et async, et surtout ne charger que ce qui sert vraiment sur chaque page.
Variantes possibles
Sur un site avec davantage de trafic anonyme, on pousse parfois la logique plus loin en chargeant certains scripts uniquement après la première interaction de l’utilisateur (scroll ou clic), via un petit gestionnaire d’événements once: true. Cette variante n’a pas été nécessaire ici, le simple report conditionnel ayant suffi à passer sous la barre des trois secondes visée par le client.