En analysant les Core Web Vitals de ce blog en octobre 2026, nous avons repéré un comportement curieux sur la page d’accueil : sur mobile, Chrome téléchargeait deux versions de la même image de couverture, celle de 768 px (8,3 Ko) et celle de 1600 px (25,8 Ko). Il finissait même par afficher la grande, sur un écran de 412 px.
Le coupable tenait en une ligne : notre balise <link rel="preload"> pour l’image LCP était placée avant la balise <meta name="viewport">. Cet article explique pourquoi cet ordre compte, comment le vérifier sur votre site et comment le corriger. Toutes les mesures ci-dessous ont été faites sur wpmoderne.fr.
Le symptôme : une image en double, et la mauvaise à l’écran
Notre page d’accueil précharge son image principale avec un imagesrcset, pour que le navigateur choisisse lui-même la bonne taille parmi cinq variantes (300, 768, 1024, 1536 et 1600 px de large). C’est la méthode recommandée pour une image responsive. Voici ce que Chrome téléchargeait réellement, page chargée sans cache :
| Écran simulé | Images téléchargées | Image affichée |
|---|---|---|
| Mobile 412 px, densité 1,75 | 768 px + 1600 px | 1600 px |
| Mobile 390 px, densité 3 | 1536 px + 1600 px | 1600 px |
| Ordinateur 1366 px, densité 1 | 1536 px | 1536 px |

<img> (ligne 706). Chrome affiche les tailles en kilo-octets décimaux (8,5 et 26,4 kB), soit les 8,3 et 25,8 Ko cités plus haut. Capture reconstituant l’état d’avant correctif.Sur ordinateur, tout va bien. Sur mobile, chaque visiteur paie un téléchargement inutile, au moment précis où la page se bat pour afficher son plus grand élément. Lighthouse le confirmait : la même couverture apparaissait deux fois dans la liste des requêtes.
La cause : un navigateur mobile croit d’abord que l’écran fait 980 px
Tant qu’il n’a pas lu <meta name="viewport" content="width=device-width">, un navigateur mobile met la page en forme sur une largeur fictive de 980 px, puis la réduit pour la faire tenir à l’écran. C’est le comportement historique, prévu pour les sites qui n’étaient pas pensés pour le mobile.
Or le navigateur lit le <head> dans l’ordre. S’il rencontre un preload responsive avant la balise viewport, il choisit dans imagesrcset pour un écran de 980 px. Sans attribut imagesizes, l’image est censée occuper toute la largeur (100vw), donc 980 px multipliés par la densité de l’écran. À densité 1,75, cela donne environ 1715 pixels physiques : seule la variante 1600 px s’en approche, il la télécharge.
Quelques lignes plus loin, il lit la balise viewport et connaît enfin la vraie largeur (412 px). Tout se passe alors comme si le preload était réévalué : il demande la variante adaptée, 768 px. Les outils de développement attribuent d’ailleurs les deux requêtes au tout début du document, là où se trouve le preload, et aucune à la balise <img>, située bien plus bas. Deux requêtes pour une seule image ; sur notre page, Chrome a ensuite affiché la version de 1600 px, déjà téléchargée.
La preuve sur une page de test isolée
Pour écarter toute autre explication, nous avons construit une page minimale : une image avec dix variantes de largeur (360, 412, 420, 600, 800, 960, 980, 1000, 1200 et 1600 px), préchargée soit avant, soit après la balise viewport. Rien d’autre sur la page.
<head>
<meta charset="utf-8">
<!-- Cas « avant » : le preload précède la balise viewport -->
<link rel="preload" as="image" href="img-800.png"
imagesrcset="img-360.png 360w, img-412.png 412w, … , img-980.png 980w, img-1600.png 1600w">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
| Écran | Preload avant la balise viewport | Preload après la balise viewport |
|---|---|---|
| 412 px, densité 1 | 412w + 980w | 412w |
| 412 px, densité 2 | 960w + 1600w | 960w |
| 1366 px, densité 1 | 1600w | 1600w |
La première ligne est sans ambiguïté : sur un écran de 412 px de densité 1, le preload placé trop tôt choisit exactement la variante de 980 px, la largeur par défaut. À densité 2, il réclame 1960 pixels et prend la plus grande disponible. Une fois le preload déplacé après la balise viewport, il n’y a plus qu’un seul téléchargement, le bon. Sur ordinateur, la largeur réelle dépasse 980 px et le problème n’apparaît pas, ce qui explique qu’il passe facilement inaperçu.
Votre site WordPress est-il concerné ?
Bonne nouvelle : le fonctionnement par défaut de WordPress est sûr. Les thèmes de blocs reçoivent leur balise viewport par le cœur, sur le crochet wp_head avec la priorité 0, et la fonction wp_preload_resources() imprime les preloads déclarés avec le filtre du même nom à la priorité 1, donc après. Les thèmes classiques comme Hello Elementor écrivent la balise viewport en dur dans header.php, avant l’appel à wp_head().
Le risque apparaît quand un preload est ajouté par un autre chemin :
- un code qui réécrit le HTML final (tampon de sortie) et insère le preload « juste après
<meta charset>», pour qu’il soit le plus haut possible. C’était notre cas ; - une fonction accrochée à
wp_headavec une priorité négative, ou un thème qui imprime sa balise viewport surwp_headavec une priorité supérieure à celle du preload ; - un preload écrit directement dans
header.php, au-dessus de la balise viewport.
Seuls les preloads responsives sont touchés, c’est-à-dire ceux qui ont un attribut imagesrcset. Un preload de police ou d’image à URL unique télécharge toujours le même fichier, où qu’il soit placé.
Vérification 1 : l’ordre dans le code source
Cette commande affiche la position, en octets, des trois balises dans le HTML servi. La balise viewport doit arriver avant tout preload qui contient imagesrcset :
curl -s https://www.example.com/ | grep -b -o '<meta charset[^>]*>\|<meta name="viewport"\|<link rel="preload"[^>]*imagesrcset'
Vérification 2 : les téléchargements en double
Ouvrez la page dans Chrome avec l’émulation mobile des outils de développement, cochez « Disable cache », rechargez, puis collez ce code dans la console. Il regroupe les images téléchargées par nom de fichier, sans le suffixe de taille ajouté par WordPress (-768x432), et signale celles qui ont été chargées en plusieurs versions :
const groupes = {};
for (const e of performance.getEntriesByType('resource')) {
if (!/\.(jpe?g|png|webp|avif)(\?|$)/i.test(e.name)) continue;
const base = e.name.split('?')[0].replace(/-\d+x\d+(?=\.)/, '');
(groupes[base] ??= []).push(e.name.split('/').pop());
}
console.table(Object.entries(groupes)
.filter(([, v]) => v.length > 1)
.map(([image, versions]) => ({ image: image.split('/').pop(), versions: versions.join(' + ') })));
Un tableau vide signifie qu’aucune image n’a été téléchargée en double. Pensez à tester avec une largeur d’écran mobile : en mode ordinateur, le problème n’apparaît pas.
Le correctif
La règle est simple : la balise viewport juste après <meta charset>, et les preloads responsives après elle. Le preload garde tout son intérêt, puisqu’il reste en tête du document, devant les feuilles de style et les scripts.
Sur ce blog, le preload était inséré par une extension maison qui réécrit le HTML final. Il a suffi de changer le point d’insertion, en gardant l’ancien comme solution de secours si la page n’a pas de balise viewport :
// Avant : juste après <meta charset>, donc avant la balise viewport.
$head = preg_replace( '#(<meta charset=[^>]*>)#i', "$1\n" . $preload, $head, 1, $count );
// Après : juste après la balise viewport, puis <meta charset> à défaut.
$head = preg_replace( '#(<meta name=["\']viewport["\'][^>]*>)#i', "$1\n" . $preload, $head, 1, $count );
if ( ! $count ) {
$head = preg_replace( '#(<meta charset=[^>]*>)#i', "$1\n" . $preload, $head, 1, $count );
}
Si vous passez par le filtre wp_preload_resources du cœur, vous n’avez rien à faire tant que votre thème respecte l’ordre décrit plus haut.
Ce que ça a changé, mesures à l’appui
Après correction, la page d’accueil ne télécharge plus qu’une version de sa couverture, quelle que soit la taille d’écran testée : 768 px sur l’écran de 412 px, 1536 px sur l’écran de densité 3 et sur ordinateur. Le poids total de la page sur mobile est passé de 455 Ko à 429 Ko, soit les 25,8 Ko de la variante 1600 px qui n’est plus téléchargée.
| Lighthouse mobile, page d’accueil | Score | LCP |
|---|---|---|
| Avant (2 passages) | 88 et 97 | 3,7 s et 2,4 s |
| Après (3 passages) | 98, 89 et 98 | 1,9 s, 3,5 s et 1,9 s |
Soyons honnêtes sur ces chiffres. Nous avons corrigé en même temps un script de mesure d’audience qui bloquait la lecture de la page : le gain de LCP ne vient donc pas uniquement du preload. Et Lighthouse varie d’un passage à l’autre, comme le montre le 3,5 s du deuxième essai. Le résultat certain, c’est la requête supprimée : 25,8 Ko de moins sur chaque première visite mobile, à l’endroit où ils coûtent le plus cher.
À retenir
- Avant la balise viewport, un navigateur mobile raisonne sur un écran de 980 px.
- Un preload avec
imagesrcsetplacé trop tôt télécharge une variante trop grande, en plus de la bonne. - Placez la balise viewport juste après
<meta charset>, et vos preloads responsives après elle. - Testez toujours en largeur mobile : sur ordinateur, ce problème est invisible.
Méthode : diagnostic et mesures réalisés le 14 octobre 2026 sur ce blog, avec l’aide d’un assistant IA pour l’écriture des scripts de test. Les chiffres proviennent de nos propres journaux, de Lighthouse et de Chrome piloté par Puppeteer.