Vous avez un bon menu. Les clients l’aiment. Il fonctionne. Mais chaque fois que vous testez votre boutique avec PageSpeed Insights, Google signale un fichier spécifique : votre script de menu. L’étiquette dit « JavaScript bloquant le rendu », le fichier fait 120 Ko, et il ajoute 600 millisecondes à votre LCP mobile. Vous savez que vous devez le corriger, mais la dernière fois que quelqu’un a suggéré d’utiliser defer, le menu a cessé de fonctionner. La moitié des menus déroulants ont arrêté de fonctionner, le bouton d’activation mobile ne faisait rien, et vous l’avez annulé en une heure.
C’est un problème courant. Les scripts bloquants le rendu sont l’un des goulots d’étranglement de performance les plus impactants sur les boutiques Shopify, et le menu est souvent un responsable. Mais différer JavaScript n’est pas juste ajouter un attribut à une balise de script. Si vous le faites mal, le menu ne fonctionnera pas. Si vous le faites bien, vous pouvez réduire votre LCP de près d’une demi-seconde sans toucher à une seule ligne de logique de menu.
Cet article explique ce que le rendu bloquant signifie réellement, pourquoi async et defer se comportent différemment, et comment charger votre script de menu sans casser la fonctionnalité.
- Les scripts bloquants le rendu interrompent tout rendu de page jusqu'à ce qu'ils se téléchargent, s'analysent et s'exécutent.
defertélécharge les scripts en parallèle mais retarde l'exécution jusqu'à la fin de l'analyse HTML.asynctélécharge et exécute immédiatement, ce qui peut casser les menus qui dépendent de l'ordre du DOM.type="module"diffère par défaut et supporte les fonctionnalités modernes de JavaScript.- La plupart des casses de menu après le report sont causées par la synchronisation de l'initialisation, pas par l'attribut defer lui-même.
Ce que le rendu bloquant signifie réellement
Quand un navigateur rencontre une balise <script> dans le HTML sans attributs de chargement, il s’arrête tout. Il suspend l’analyse HTML, télécharge le script, l’analyse, l’exécute, et seulement ensuite continue la construction de la page. C’est ce qu’on appelle le comportement synchrone ou bloquant le rendu.
La logique derrière ce comportement est ancienne mais raisonnable : JavaScript peut modifier le DOM en utilisant document.write(), donc le navigateur suppose que chaque script pourrait changer la structure de la page et doit donc s’exécuter avant de continuer.
Voici le problème. Votre script de menu n’utilise pas document.write(). Il n’a pas besoin de s’exécuter avant que le reste de la page soit analysé. Mais le navigateur ne le sait pas, donc il bloque quand même.
Selon la propre documentation de Chrome sur l’optimisation de Largest Contentful Paint, éliminer le JavaScript bloquant le rendu est l’une des améliorations les plus impactantes que vous puissiez faire. Quand un script dans l’en-tête du document bloque pendant 400 millisecondes, chaque élément en dessous dans le DOM — votre image héros, votre titre, votre grille de produits — attend 400 millisecondes de plus pour être peint.
Les trois stratégies de chargement : par défaut, defer et async
Il y a trois façons pour un navigateur de charger un script.
Par défaut (Synchrone)
<script src="menu.js"></script>
Le navigateur arrête l’analyse HTML, télécharge menu.js, l’analyse, l’exécute, puis reprend. Cela bloque le rendu pendant toute la durée.
Defer
<script src="menu.js" defer></script>
Le navigateur télécharge menu.js en parallèle avec l’analyse HTML mais ne l’exécute pas jusqu’à ce que tout le document HTML ait été analysé. Plusieurs scripts différés s’exécutent dans l’ordre dans lequel ils apparaissent dans le HTML.
Async
<script src="menu.js" async></script>
Le navigateur télécharge menu.js en parallèle avec l’analyse HTML et l’exécute dès que le téléchargement est terminé, même si l’analyse HTML n’est pas terminée. L’ordre d’exécution est imprévisible si plusieurs scripts utilisent async.
Voici un tableau résumant le comportement :
| Attribut | Téléchargement | Exécution | Bloque l’analyse HTML | Ordre d’exécution |
|---|---|---|---|---|
| Aucun (par défaut) | Bloque l’analyse | Immédiat | Oui | Séquentiel |
defer |
Parallèle | Après l’analyse HTML | Non | Séquentiel |
async |
Parallèle | Dès que prêt | Oui (pendant l’exécution) | Imprévisible |
Pour la plupart des menus de navigation, defer est le bon choix. Il maintient les scripts dans l’ordre, ce qui importe quand un script dépend d’un autre (par exemple, un script de menu qui s’appuie sur une bibliothèque utilitaire). Et il garantit que le DOM est prêt avant que le script s’exécute, ce qui est critique pour tout code qui interroge les éléments.
Pourquoi Defer casse certains menus (et comment le corriger)
Vous ajoutez defer à votre balise de script de menu, rechargez la page, et le menu ne fonctionne pas. Les menus déroulants ne s’ouvrent pas. Le bouton d’activation mobile ne fonctionne pas. Vous supprimez defer, et tout fonctionne à nouveau. Qu’est-ce qui s’est passé ?
Le problème est presque toujours une synchronisation d’initialisation. Voici les trois causes les plus courantes.
Problème 1 : Le script s’exécute avant que le JavaScript du thème soit prêt
De nombreux thèmes Shopify ont un objet JavaScript global ou une fonction d’initialisation sur lesquels d’autres scripts dépendent. Si votre script de menu s’exécute avant que cet objet soit créé, il échoue.
Exemple :
// theme.js (chargé en premier)
window.Theme = { utils: { ... } };
// menu.js (chargé en second, s'attend à ce que Theme existe)
Theme.utils.initDropdown();
Si menu.js est différé mais que theme.js ne l’est pas, menu.js pourrait s’exécuter avant que theme.js finisse d’exécuter, et Theme sera undefined.
Correction : Différez les deux scripts, ou assurez-vous que menu.js vérifie les dépendances avant d’initialiser.
if (typeof Theme !== 'undefined') {
Theme.utils.initDropdown();
}
Problème 2 : Les scripts en ligne s’exécutent avant les scripts différés
Les scripts en ligne (JavaScript écrit directement dans le HTML, pas chargé à partir d’un fichier externe) s’exécutent dans l’ordre du document à mesure qu’ils sont rencontrés. Les scripts différés s’exécutent après que tout le HTML ait été analysé, ce qui signifie après tous les scripts en ligne.
Si vous avez un script en ligne qui essaie d’initialiser le menu, et que la bibliothèque de menu est différée, l’initialisation échouera.
Correction : Déplacez la logique d’initialisation dans le script différé, ou enveloppez l’initialisation en ligne dans un listener DOMContentLoaded.
document.addEventListener('DOMContentLoaded', function() {
if (typeof MenuApp !== 'undefined') {
MenuApp.init();
}
});
Problème 3 : Conditions de course avec plusieurs scripts différés
Si votre menu dépend d’une bibliothèque utilitaire, et que les deux sont différés, ils devraient s’exécuter dans l’ordre dans lequel ils apparaissent dans le HTML. Mais si la bibliothèque utilitaire est chargée de manière asynchrone (en utilisant async au lieu de defer), l’ordre d’exécution devient imprévisible.
Correction : Utilisez defer pour tous les scripts interdépendants, pas async. Si vous devez utiliser async, ajoutez des vérifications de dépendance explicites ou utilisez des imports dynamiques.
Type=”module” : l’alternative moderne
Le JavaScript moderne supporte les modules ES6, qui ont un chargement différé intégré.
<script type="module" src="menu.js"></script>
Les scripts avec type="module" diffèrent automatiquement. Ils se téléchargent en parallèle et s’exécutent après la fin de l’analyse HTML, tout comme defer. Ils supportent également import et export, ce qui rend la gestion des dépendances explicite.
Si votre script de menu est écrit en tant que module, vous pouvez importer les dépendances directement :
import { initDropdown } from './dropdown.js';
initDropdown();
Cela élimine les problèmes de synchronisation qui causent des casses avec defer traditionnel, car le navigateur sait exactement quels scripts dépendent de quels autres.
L’inconvénient : les navigateurs plus anciens (Internet Explorer, très anciennes versions de Safari) ne supportent pas les modules. Mais selon Can I Use, plus de 95 % du trafic de navigateurs mondial supporte les modules ES6 à partir de 2025. Pour la plupart des boutiques Shopify, c’est sûr.
Considérations spécifiques à Shopify
Les thèmes Shopify chargent les scripts de différentes façons selon l’architecture du thème. Voici comment le report fonctionne dans les modèles les plus courants.
Balises de script Theme.liquid
Si votre script de menu est chargé dans theme.liquid comme ceci :
{{ 'menu.js' | asset_url | script_tag }}
Le filtre script_tag n’ajoute pas defer par défaut. Vous devez le remplacer par une balise <script> manuelle :
<script src="{{ 'menu.js' | asset_url }}" defer></script>
Blocs d’application intégrés
Si votre menu est installé en tant que bloc d’application intégré (courant pour les applications de menu tierces), l’application contrôle la façon dont son script est chargé. Vous ne pouvez pas ajouter defer vous-même. Vous devez vérifier auprès du développeur de l’application ou basculer vers une application de menu qui charge déjà les scripts de manière asynchrone.
Des outils comme Navi+ chargent leur JavaScript avec defer par défaut, ce qui est l’une des raisons pour lesquelles ils performent mieux dans les tests LCP que les applications de menu héritage.
Rendu de sections et scripts différés
Les thèmes Shopify 2.0 chargent les sections dynamiquement. Si une section inclut un script différé, et que la section est ajoutée à la page après le chargement initial (par exemple, via AJAX pour aperçu rapide ou défilement infini), le script pourrait ne pas s’exécuter comme prévu.
Correction : Utilisez DOMContentLoaded uniquement pour le chargement initial de la page. Pour les sections ajoutées dynamiquement, déclenchez l’initialisation manuellement en utilisant un événement personnalisé ou un observateur de mutation.
Comment tester si Defer fonctionne
Avant de déployer le chargement de scripts différés sur votre boutique en direct, testez-le à fond. Voici une liste de contrôle.
- Chrome de bureau : Ouvrez votre boutique, ouvrez DevTools, allez à l’onglet Réseau, rechargez, et vérifiez que le script de menu se télécharge en parallèle avec d’autres ressources et ne bloque pas le document.
- Simulation mobile : Dans DevTools, basculez sur l’émulation mobile (iPhone ou Android), limitez le réseau à « 3G rapide », et rechargez. Le menu devrait toujours s’initialiser correctement.
- Multi-navigateur : Testez dans Safari, Firefox et Edge. Les scripts de module se comportent légèrement différemment dans Safari.
- Appareil réel : Ouvrez la boutique sur un vrai téléphone sur une vraie connexion mobile. L’émulation est utile, mais la latence du monde réel expose les bugs de synchronisation que les tests de bureau manquent.
- Vérification de fonctionnalité : Ouvrez chaque menu déroulant, testez le bouton d’activation du menu mobile, cliquez sur chaque lien, et vérifiez que rien ne casse.
Avant de pousserExécutez un test PageSpeed Insights sur la même page avant et après l'activation du report. Vous devriez voir une baisse mesurable de la LCP et une réduction des ressources bloquant le rendu. Si la LCP n'améliore pas, le script pourrait ne pas avoir été le goulot d'étranglement, ou il pourrait y avoir d'autres ressources bloquantes à traiter.
Quand Async a du sens (et quand ce n’est pas le cas)
async est approprié pour les scripts qui sont complètement autonomes et ne dépendent pas de l’ordre du DOM ou d’autres scripts. Les exemples incluent les traceurs d’analytics, les widgets de chat, et certains scripts de publicités.
Pour les menus de navigation, async est risqué. Le menu a besoin que le DOM soit prêt, il dépend souvent des utilitaires de thème, et il doit généralement s’exécuter avant que le client n’interagisse avec la page. Si le script s’exécute pendant que le HTML est toujours en cours d’analyse, il pourrait essayer d’interroger des éléments qui n’existent pas encore.
Si vous envisagez async pour un script de menu, le script doit :
- Ne pas dépendre d’autres scripts
- Ne pas interroger les éléments du DOM (ou envelopper toutes les requêtes dans une vérification
DOMContentLoaded) - Être suffisamment petit pour que son temps d’exécution ne bloque pas l’analyse
La plupart des scripts de menu ne répondent pas à ces critères. Tenez-vous à defer ou type="module" sauf si vous avez une raison spécifique d’utiliser async.
À quoi ressemble un bon chargement de script de menu
Un script de menu correctement optimisé sur une boutique Shopify ressemble à ceci :
<script src="{{ 'menu.js' | asset_url }}" defer></script>
Ou, si vous utilisez des modules :
<script type="module" src="{{ 'menu.js' | asset_url }}"></script>
Le script se télécharge en parallèle avec le reste de la page, s’exécute après que le DOM soit prêt, et ne bloque pas l’image héros ou tout autre élément LCP de se peindre.
Dans la timeline Performance de Chrome DevTools, vous devriez voir l’image héros se peindre avant que le script de menu s’exécute. Si le script de menu s’exécute en premier, il bloque toujours.
La plupart des boutiques peuvent couper 300 à 600 millisecondes de LCP mobile juste en passant du chargement de script synchrone au chargement différé. Le menu fonctionne toujours. Le client ne remarque pas de différence. Mais Google le remarque, et plus important encore, le navigateur le remarque. La page devient visible plus rapidement, ce qui est tout l’intérêt.
Cet article fait partie du guide plus large sur Menu et LCP : comment la navigation bloque votre plus grand élément de contenu.