
⚡ Comment ajouter defer et async pour les scripts WordPress dans function.php
Les pages se chargent lentement, Google PageSpeed Insights affiche des avertissements orange et le client demande: «pourquoi le site est-il lent?» Neuf fois sur dix, la cause première est le JavaScript qui bloque le rendu. Le navigateur rencontre une balise <script>, interrompt la construction du DOM, charge et exécute le script, puis reprend seulement ensuite. Sur un site moderne avec une dizaine de plugins, ce délai se transforme en secondes.
WordPress n’a longtemps proposé aucun moyen standard pour contrôler le chargement des scripts. Les développeurs jonglaient avec des contournements: filtrer script_loader_tag, modifier la sortie via clean_url, voire écrire des walkers personnalisés pour WP_Scripts. Mais avec la sortie de WordPress 6.3, la situation a radicalement changé et nous disposons désormais d’une méthode propre et officielle pour ajouter defer ou async à n’importe quel script, sans la moindre bidouille.
Voici deux méthodes opérationnelles: l’approche native moderne (WP 6.3+) et le filtre éprouvé script_loader_tag (WP 4.1+). Toutes deux ont été testées sur des projets réels et préservent l’intégrité de la file de dépendances.
💡 Aperçu rapide:
- Comprenez la différence entre
deferetasyncet quand utiliser chacun, car c’est cela qui détermine si les fonctionnalités cassent après l’optimisation - Utilisez la méthode native de WordPress 6.3+ via
wp_enqueue_script()avec le paramètrestrategy, l’approche la plus propre qui préserve l’ordre d’exécution - Si le site tourne sur une version inférieure à 6.3, appliquez le filtre
script_loader_tagavec un tableau de handles, cela fonctionne à partir de WordPress 4.1 - Pour plusieurs scripts, regroupez les handles dans un tableau et parcourez-le avec une boucle, un seul filtre pour tous les scripts au lieu de copier-coller
Ce que sont defer et async et quand les utiliser
Quand un navigateur rencontre une balise <script> classique, il fait trois choses en séquence: il arrête l’analyse du HTML, charge le script, l’exécute. Ensuite seulement il reprend le HTML. Sur une page avec cinq scripts dans le <head>, cela signifie que l’utilisateur voit un écran blanc pendant que le dernier plugin de commentaires se charge, alors que l’article lui-même aurait pu s’afficher depuis longtemps.
Les attributs defer et async résolvent ce problème, mais fonctionnent différemment:
Attribut | Quand il se charge | Quand il s’exécute | Ordre d’exécution |
|---|---|---|---|
(aucun) | Bloque l’analyse immédiatement | Immédiatement après le chargement | Dans l’ordre du DOM |
| En parallèle de l’analyse | Une fois le DOM entièrement chargé | Dans l’ordre du DOM |
| En parallèle de l’analyse | Immédiatement après le chargement | Le premier chargé s’exécute en premier |
Defer est le couteau suisse pour la plupart des scénarios. Le script se charge en parallèle du HTML et ne s’exécute que lorsque le DOM est entièrement construit. L’ordre est préservé: le script A s’exécutera avant le script B, même si B s’est chargé plus vite. C’est essentiel pour jQuery et tout ce qui en dépend.
Async est un outil pour les scripts indépendants. Analytics, publicités, widgets de réseaux sociaux: ils n’ont pas besoin du DOM, ils ne se soucient pas de l’ordre, ils doivent juste s’exécuter dès que possible. Mais si vous mettez async sur un script qui dépend de jQuery, vous obtiendrez probablement $ is not defined.
Règle simple: le script dépend d’autres scripts ou du DOM → defer. Le script est totalement autonome → async. En cas de doute, commencez toujours par defer.
Méthode 1: approche native de WordPress 6.3+
Depuis juillet 2023, un nouveau mécanisme est opérationnel dans le cœur de WordPress. Les fonctions wp_register_script() et wp_enqueue_script() ont reçu un cinquième paramètre surchargé $args, un tableau où vous pouvez spécifier la stratégie de chargement. Pas de filtres, pas de manipulation de chaînes, aucun risque de casser l’ordre des dépendances.
Syntaxe de base pour defer:
1 wp_enqueue_script( 2 'my-js-handle', 3 get_template_directory_uri() . '/js/my-script.js', 4 array('jquery'), 5 '1.0.0', 6 array( 7 'strategy' => 'defer', 8 'in_footer' => true, 9 ) 10 );
Pour async, même mécanique:
1 wp_enqueue_script( 2 'google-analytics', 3 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX', 4 array(), 5 '1.0.0', 6 array( 7 'strategy' => 'async', 8 'in_footer' => false, 9 ) 10 );
La clé in_footer à l’intérieur du tableau fonctionne comme l’ancien paramètre booléen: true place le script dans le pied de page, false dans le <head>. Pour defer, on met généralement true (le script attend de toute façon le DOM, inutile de le charger tôt), pour async, c’est selon les besoins.
Le principal avantage de la méthode native est que le cœur vérifie lui-même l’arbre des dépendances. Si le script A avec defer dépend du script B, et que B est enregistré sans stratégie (bloquant), WordPress ne cassera pas votre site: il rétrogradera automatiquement la stratégie du script A en bloquant. Avec script_loader_tag, vous n’avez pas cette protection, le filtre se contente d’insérer l’attribut sans examiner les dépendances.
Important: le tableau $args est apparu dans WordPress 6.3. Si un thème ou un plugin doit fonctionner sur des versions antérieures, utilisez la méthode 2 ou ajoutez une vérification:
1 if ( version_compare( $GLOBALS['wp_version'], '6.3', '>=' ) ) { 2 // native method 3 } else { 4 // script_loader_tag filter 5 }
Méthode 2: filtre script_loader_tag (WordPress 4.1+)
Si le site tourne sur une version inférieure à 6.3 ou si vous devez maintenir la rétrocompatibilité, appliquez le filtre éprouvé script_loader_tag. Il existe depuis WordPress 4.1 et fonctionne toujours parfaitement.
Le filtre se déclenche juste avant que la balise <script> ne soit envoyée dans le HTML, vous recevez la chaîne de la balise prête, le handle du script et le chemin du fichier, et vous pouvez remplacer src par defer="defer" src ou async="async" src.
Script unique avec defer:
1 function add_defer_to_my_script($tag, $handle) { 2 if ( 'my-js-handle' !== $handle ) { 3 return $tag; 4 } 5 return str_replace( ' src', ' defer="defer" src', $tag ); 6 } 7 add_filter('script_loader_tag', 'add_defer_to_my_script', 10, 2);
Le code se place dans le functions.php du thème actif ou, plus correctement, dans un plugin de snippets dédié comme Code Snippets ou WPCode. Si vous le mettez dans le functions.php d’un thème enfant, lorsque vous changerez de thème, les scripts redeviendront bloquants et vous ne le remarquerez pas immédiatement.
Le handle du script est le premier paramètre que vous avez passé à wp_register_script() ou wp_enqueue_script(). C’est ce qui apparaît dans la condition if. Ne devinez pas le handle, ouvrez le code source du plugin ou du thème et trouvez l’appel à wp_enqueue_script.
Defer et async pour plusieurs scripts
Ajouter un filtre par script, c’est la voie vers un functions.php obèse et des erreurs de copier-coller. La bonne solution: un tableau de handles et un seul filtre avec une boucle.
1 function add_defer_to_scripts($tag, $handle) { 2 $scripts_to_defer = array( 3 'my-js-handle', 4 'another-handle', 5 'third-party-lib', 6 ); 7 8 foreach ( $scripts_to_defer as $defer_script ) { 9 if ( $defer_script === $handle ) { 10 return str_replace( ' src', ' defer="defer" src', $tag ); 11 } 12 } 13 return $tag; 14 } 15 add_filter('script_loader_tag', 'add_defer_to_scripts', 10, 2);
Pour async, seuls l’attribut et le nom du tableau changent:
1 function add_async_to_scripts($tag, $handle) { 2 $scripts_to_async = array( 3 'google-tag-manager', 4 'facebook-pixel', 5 'hotjar', 6 ); 7 8 foreach ( $scripts_to_async as $async_script ) { 9 if ( $async_script === $handle ) { 10 return str_replace( ' src', ' async="async" src', $tag ); 11 } 12 } 13 return $tag; 14 } 15 add_filter('script_loader_tag', 'add_async_to_scripts', 10, 2);
Les deux filtres peuvent être accrochés simultanément, defer sur vos scripts, async sur les trackers tiers. Ils fonctionnent indépendamment et n’entrent pas en conflit.
Exemple pratique: API Google Maps
Google Maps est un candidat classique pour defer. La carte se trouve généralement dans le pied de page de la page contacts, le script pèse plus de 100 Ko et l’utilisateur n’a pas besoin de la carte immédiatement. De plus, l’API elle-même ne dépend pas des autres scripts de la page, un cas idéal.
Connexion et defer:
1 // theme's functions.php 2 function enqueue_google_maps() { 3 if ( ! is_page('contacts') ) { 4 return; 5 } 6 7 wp_enqueue_script( 8 'google-maps-api', 9 'https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY', 10 array(), 11 null, 12 array( 13 'strategy' => 'defer', 14 'in_footer' => true, 15 ) 16 ); 17 } 18 add_action('wp_enqueue_scripts', 'enqueue_google_maps');
Même résultat via script_loader_tag:
1 function add_defer_to_google_maps($tag, $handle) { 2 if ( 'google-maps-api' !== $handle ) { 3 return $tag; 4 } 5 return str_replace( ' src', ' defer="defer" src', $tag ); 6 } 7 add_filter('script_loader_tag', 'add_defer_to_google_maps', 10, 2);
Après avoir installé l’une ou l’autre variante, vérifiez impérativement la carte sur la page contacts. Ouvrez la console du navigateur (F12), assurez-vous qu’il n’y a pas d’erreurs JavaScript et que la carte s’affiche correctement. Si vous obtenez une erreur du type initMap is not a function, cela signifie que votre script d’initialisation doit également être marqué comme defer et placé strictement après la connexion à l’API.
Comment vérifier que defer et async fonctionnent
Après la mise en œuvre vient la vérification. Sans elle, vous ne savez pas si l’optimisation a fonctionné ou si elle reste du code mort.
Ouvrez le code source de la page (Ctrl+U) et cherchez vos scripts. La balise <script> doit avoir les attributs:
1 <script defer="defer" src="/wp-content/themes/my-theme/js/my-script.js"></script>
S’il n’y a pas d’attributs, vérifiez si le handle dans le filtre correspond bien au handle réel du script. Erreur fréquente: dans wp_enqueue_script, le handle est my-plugin-frontend, mais dans le filtre, c’est my_plugin_frontend. Tiret contre underscore, et le filtre ignore silencieusement le script.
Dernière touche, Google PageSpeed Insights ou Lighthouse dans l’onglet Audits des outils de développement. La section «Éliminer les ressources qui bloquent le rendu» devrait montrer une amélioration. Le gain précis dépend du nombre et de la taille des scripts, mais pour un site WordPress typique avec 5 à 7 plugins, une réduction de 40 à 60% du JavaScript bloquant est un résultat atteignable.
⁉️🤔 Foire aux questions
Puis-je utiliser à la fois defer et async sur un même script?
Non. Si vous spécifiez les deux attributs simultanément, le navigateur ignorera
deferet exécutera le script enasync. Ce comportement est inscrit dans la spécification HTML,asynca toujours la priorité. Choisissez l’un ou l’autre selon que l’ordre d’exécution est important ou non.
Que faire si, après avoir ajouté defer, le script ne fonctionne plus?
Très probablement, le script s’attend à ce que le DOM ne soit pas encore construit et tente de manipuler des éléments qui n’existent pas au moment de l’exécution. Remplacez
deferpar un chargement bloquant standard pour ce script spécifique. Ou bien enveloppez le code du script dansDOMContentLoaded, il pourra alors fonctionner avecdefersans erreur. La seconde option est préférable: vous conservez l’optimisation et corrigez la compatibilité.
Quelle est la différence entre defer et déplacer le script dans le pied de page via wp_enqueue_script avec $in_footer = true?
$in_footer = truene fait que déplacer la balise<script>du<head>à la fin du<body>. Le script bloque toujours le rendu, simplement plus tard.deferse charge en parallèle de l’analyse HTML et s’exécute strictement après la construction du DOM. L’utilisation combinée (in_footer => true+strategy => 'defer') donne un effet maximal: le script dans le pied de page ne retarde pas le premier rendu, et defer garantit qu’il ne bloquera pas non plus le rendu final.
Dois-je mettre à jour WordPress vers 6.3 juste pour la méthode native?
Si le site est en version 6.2 ou antérieure, la mise à jour vaut la peine, pas seulement pour
strategy. WordPress 6.3 a corrigé des dizaines de vulnérabilités et apporté des améliorations de performance au cœur. Mais si une mise à jour est impossible pour une raison quelconque, le filtrescript_loader_tagfonctionne de manière absolument fiable depuis la version 4.1, sortie en 2014. Vous ne perdez rien à l’utiliser.
Et jQuery, defer ou le laisser tel quel?
jQuery devrait se charger avec
defersi tous les scripts dépendants sont également marquésdefer. Le problème est que les plugins WordPress gèrent extrêmement rarement les attributs de leurs scripts. Si vous mettezdefersur jQuery alors qu’un plugin de formulaire de contact connecte son script sans attribut, le navigateur exécutera le plugin avant jQuery et le formulaire cassera. Conseil pratique: commencez pardeferpour les scripts de votre propre thème. Ne touchez pas à jQuery avant d’avoir testé chaque plugin du site.
Ce qu’il faut mettre sur un site en production en 2026
Si le serveur tourne sous WordPress 6.3 ou plus récent, uniquement la méthode native. Code propre, protection contre les conflits de dépendances, support du cœur. Commencez par defer pour tous les scripts du thème et les plugins critiques; réservez async pour l’analytique et les widgets tiers.
Si la version est inférieure à 6.3, le filtre script_loader_tag avec un tableau de handles. Cela fonctionne depuis une décennie, rien ne peut casser. La seule chose qu’il ne peut pas faire, c’est vérifier automatiquement l’arbre des dépendances, alors ajoutez les scripts au tableau un par un et vérifiez le site après chaque ajout.
Et surtout: aucune méthode ne remplace l’audit des scripts eux-mêmes. Si un plugin de galerie connecte 15 fichiers juste pour afficher trois images, ni defer ni async n’aideront radicalement. L’optimisation du chargement commence par la question «ce script est-il vraiment nécessaire», et seulement ensuite, «comment le charger».
🔗 Documentation officielle de WordPress 6.3, Stratégies de chargement des scripts



