Le problème de performance

Si vous avez déjà vu une page mobile charger “vite” puis sauter de partout pendant 2 secondes, vous avez probablement un combo toxique : images trop lourdes + responsive images mal configurées + dimensions absentes. Sur WordPress 6.9.4, le cœur sait générer srcset et sizes, mais il suffit d’un thème, d’un builder ou d’un snippet “optimisation” un peu agressif pour casser la chaîne.

Le symptôme concret : score mobile en baisse, LCP (Largest Contentful Paint) dominé par une image “hero”, CLS (Cumulative Layout Shift) provoqué par des images sans width/height, et des transferts inutiles (une image 2000px envoyée à un écran 390px). J’ai souvent croisé ça sur des sites Elementor/Avada où un widget injecte son propre HTML d’image et neutralise les attributs WordPress.

À la fin, vous saurez :

  • diagnostiquer précisément quelles images plombent le mobile (et pourquoi) avec du code ;
  • laisser WordPress 6.9.4 générer un srcset correct (ou le corriger proprement) ;
  • ajuster sizes selon le contexte (contenu, colonnes, builders) ;
  • améliorer LCP sans “désactiver le lazy” n’importe où.

Résumé rapide

  • Ne générez pas vos <img> à la main : utilisez wp_get_attachment_image() ou the_post_thumbnail() pour obtenir srcset, sizes et les dimensions.
  • Corrigez sizes par contexte (contenu vs sidebar vs grille) via le filtre wp_calculate_image_sizes.
  • Traitez l’image LCP : fetchpriority="high", preload ciblé, et surtout une taille réellement adaptée au viewport mobile.
  • Ne “désactivez” pas le lazy-loading globalement : évitez-le uniquement sur l’image LCP et sur certains sliders.
  • Activez WebP/AVIF côté génération et serveur, mais surveillez les plugins qui remplacent les URLs et cassent le srcset.

Diagnostic avec du code

1) Activer un mode debug exploitable (sans tout casser)

Sur un site de production, évitez WP_DEBUG_DISPLAY. Logguez, puis analysez.

<?php
// wp-config.php (WordPress 6.9.4+, PHP 8.1+)

// Active le debug
define('WP_DEBUG', true);

// Log dans wp-content/debug.log
define('WP_DEBUG_LOG', true);

// Ne pas afficher aux visiteurs
define('WP_DEBUG_DISPLAY', false);

// Optionnel : utile si vous traquez un plugin qui spamme des notices
@ini_set('display_errors', '0');

2) Mesurer (simplement) le poids des images et la présence de srcset/sizes

Le navigateur est la source de vérité, mais vous pouvez déjà détecter des pages “suspectes” côté PHP : images sans srcset, sans sizes, ou sans dimensions. Ce mu-plugin loggue un résumé par requête (évitez de le laisser actif en permanence).

<?php
/**
 * Plugin Name: Perf Mobile - Audit Images (mu-plugin)
 * Description: Log des indicateurs simples sur les <img> rendues (srcset/sizes/dimensions).
 */

add_action('template_redirect', function () {
	if (is_admin() || wp_doing_ajax() || wp_is_json_request()) {
		return;
	}

	ob_start(function ($html) {
		// Analyse naïve mais utile : on cherche les <img ...>
		if (!preg_match_all('/<imgb[^>]*>/i', $html, $matches)) {
			return $html;
		}

		$total = count($matches[0]);
		$missing_srcset = 0;
		$missing_sizes  = 0;
		$missing_dims   = 0;

		foreach ($matches[0] as $img) {
			if (stripos($img, 'srcset=') === false) {
				$missing_srcset++;
			}
			if (stripos($img, 'sizes=') === false) {
				$missing_sizes++;
			}
			$has_w = preg_match('/bwidth=("|')?d+/i', $img);
			$has_h = preg_match('/bheight=("|')?d+/i', $img);
			if (!$has_w || !$has_h) {
				$missing_dims++;
			}
		}

		error_log(sprintf(
			'[perf-images] %s | imgs=%d | no-srcset=%d | no-sizes=%d | no-dims=%d',
			wp_get_raw_referer() ?: (is_singular() ? get_permalink() : home_url(add_query_arg([]))),
			$total,
			$missing_srcset,
			$missing_sizes,
			$missing_dims
		));

		return $html;
	});
}, 0);

Dans mon expérience, si vous voyez beaucoup de no-srcset sur des pages où vous attendez des images “WordPress” (featured image, images du contenu), c’est presque toujours un builder ou un plugin d’optimisation qui réécrit le HTML.

3) Query Monitor + repérage des requêtes lentes liées aux médias

Pour Query Monitor, je vous laisse l’installation : Query Monitor (wordpress.org). Côté code, ce qui vous intéresse pour les images, ce sont surtout :

  • les requêtes postmeta qui explosent (thème qui récupère 20 tailles d’images une par une) ;
  • les appels répétés à wp_get_attachment_metadata() ou image_downsize() dans une boucle.

Si vous voulez un signal sans UI, activez le log des requêtes lentes MySQL (si vous avez la main serveur). C’est souvent révélateur sur des pages “grille d’articles” avec thumbnails.

4) WP-CLI : vérifier les tailles disponibles et régénérer si nécessaire

Quand le srcset est mauvais, la cause peut être bête : pas de tailles intermédiaires générées, ou un changement de tailles après import. Avec WP-CLI :

# Liste des tailles déclarées (thème + plugins)
wp eval 'print_r( wp_get_registered_image_subsizes() );'

# Vérifie la version WP / PHP
wp core version
php -v

Pour régénérer, vous passerez généralement par une extension (ex: “Regenerate Thumbnails”). Je n’inclus pas de commande “magique” ici, parce que selon votre stack, régénérer 50k images en prod sans stratégie peut être un incident.


Étape 1 : Nettoyer le HTML <img> pour laisser WordPress générer srcset/sizes

Ce qui ralentit (AVANT)

Anti-pattern classique : HTML d’image codé à la main, sans srcset, sans sizes, parfois sans dimensions. Sur mobile, vous servez une image trop grande, et le navigateur ne peut pas choisir la bonne variante.

<?php
// Template.php (lent / fragile)
$img_url = get_post_meta(get_the_ID(), 'hero_image_url', true);

echo '<img src="' . esc_url($img_url) . '" alt="" class="hero">';
// Pas de srcset, pas de sizes, pas de width/height => LCP + CLS + octets inutiles

Ce qui accélère (APRÈS)

Stockez un ID de pièce jointe (attachment ID), pas une URL. Ensuite, laissez WordPress fabriquer le markup via wp_get_attachment_image(). Le cœur utilise les métadonnées (tailles générées) pour produire srcset, sizes et width/height.

<?php
// Template.php (optimisé / WordPress-native)
$hero_id = (int) get_post_meta(get_the_ID(), 'hero_image_id', true);

if ($hero_id) {
	echo wp_get_attachment_image(
		$hero_id,
		'large', // Taille logique (sera adaptée via srcset)
		false,
		[
			'class' => 'hero',
			'alt'   => get_post_meta($hero_id, '_wp_attachment_image_alt', true) ?: get_the_title(),
		]
	);
}

Mesure d’impact (simple et réaliste)

Mesurer “le gain” côté PHP n’a pas beaucoup de sens ici : le vrai gain est réseau + rendu. Par contre, vous pouvez instrumenter le temps de génération du markup si vous suspectez un thème qui fait des traitements lourds.

<?php
// Petit chronométrage (log) autour d'un rendu d'image
$start = hrtime(true);

$html = wp_get_attachment_image($hero_id, 'large', false, ['class' => 'hero']);

$ms = (hrtime(true) - $start) / 1e6;
error_log(sprintf('[perf] wp_get_attachment_image hero: %.2f ms', $ms));

echo $html;

En pratique, l’amélioration la plus visible vient surtout du fait que le navigateur choisit une ressource plus petite via srcset. Sur un site de blog, j’ai déjà vu des pages passer de 1.4 Mo d’images transférées à 450 Ko sur mobile juste en supprimant du HTML d’images “fait maison”.

Compatibilité Divi 5 / Elementor / Avada

  • Elementor : certains widgets “Image” peuvent sortir un markup correct, mais dès que vous utilisez des champs dynamiques (ACF/Meta) ou des addons, vous retombez sur une URL simple. Préférez un champ qui retourne un ID, ou un rendu via shortcode custom qui appelle wp_get_attachment_image().
  • Divi 5 : même logique. Un module custom (ou un shortcode) qui reçoit un ID d’attachment est plus fiable qu’une URL.
  • Avada : attention aux options “lazy load”/“image optimization” du thème qui réécrivent le HTML. Testez avec ces options désactivées temporairement pour vérifier si le srcset revient.

Étape 2 : sizes intelligent par contexte et par breakpoints

Le problème : srcset existe, mais sizes ment

WordPress génère souvent un sizes générique, basé sur la largeur max du contenu. Sur une page avec sidebar, grille, ou module builder en colonnes, ce sizes peut être trop large. Résultat : le navigateur choisit une source plus grosse que nécessaire.

Je vois ça souvent sur des pages “blog” en 3 colonnes : chaque carte fait ~320px sur mobile, mais sizes annonce 768px. Le navigateur télécharge donc une image 768w.

Solution : filtrer wp_calculate_image_sizes par contexte

Le filtre wp_calculate_image_sizes permet d’ajuster la chaîne sizes calculée. On va :

  • cibler les images du contenu (classe wp-image-) ;
  • sur les archives/grilles, annoncer une largeur plus petite ;
  • sur une sidebar, annoncer encore plus petit.
<?php
/**
 * Ajuste l'attribut sizes pour éviter de sur-télécharger des images sur mobile.
 * Testé sur WordPress 6.9.4+.
 */
add_filter('wp_calculate_image_sizes', function ($sizes, $size, $image_src, $image_meta, $attachment_id) {

	// Ne touchez pas à l'admin ni aux requêtes API
	if (is_admin() || wp_is_json_request()) {
		return $sizes;
	}

	// $size peut être [width, height] ou un nom de taille selon le contexte
	$requested_width = is_array($size) ? (int) $size[0] : 0;

	// Cas 1 : thumbnails en archive (grilles d'articles)
	if (is_home() || is_archive() || is_search()) {
		// Hypothèse réaliste : grille 2 colonnes sur mobile, 3 sur tablette+
		// Ajustez selon votre CSS réel (sinon vous "mentez" au navigateur).
		return '(max-width: 480px) 48vw, (max-width: 1024px) 30vw, 320px';
	}

	// Cas 2 : images dans une sidebar (si votre thème ajoute une classe dédiée)
	// Exemple : widgets qui enveloppent les images dans .sidebar
	if (is_active_sidebar('sidebar-1')) {
		// Sizings conservateurs pour une colonne étroite
		return '(max-width: 480px) 92vw, 300px';
	}

	// Cas 3 : fallback - gardez le comportement WP
	return $sizes;

}, 10, 5);

Pourquoi c’est plus rapide

srcset propose plusieurs largeurs. sizes explique au navigateur la largeur réelle d’affichage selon le viewport. Si sizes est trop large, le navigateur choisit une image trop grande. Corriger sizes réduit les octets transférés et accélère le décodage/rendu (souvent visible sur LCP et sur le temps CPU mobile).

Edge cases

  • Si votre CSS change en fonction d’un container (layout builder), vw peut être trompeur. Mesurez vos largeurs réelles.
  • Ne mettez pas un sizes “trop petit” : vous forcerez le navigateur à choisir une image trop basse résolution (flou sur écrans denses).
  • Si vous avez un CDN qui “normalise” les URLs (ex: paramètres), vérifiez que toutes les entrées du srcset pointent vers des URLs valides.

Étape 3 : Éviter les images “hero” trop lourdes (preload, priorité et LCP)

Le piège classique

Vous activez le lazy-loading partout. Résultat : l’image “hero” (souvent l’élément LCP) est chargée tard, car elle attend l’intersection observer. Le score LCP se dégrade, même si “moins d’images” partent au départ.

1) Désactiver le lazy uniquement sur l’image LCP

WordPress ajoute loading="lazy" sur beaucoup d’images. Le filtre wp_lazy_loading_enabled permet de contrôler. Ici, on désactive le lazy pour la première image du contenu sur les pages singulières, et pour l’image mise en avant si elle est rendue via the_post_thumbnail() (cas fréquent).

<?php
/**
 * Désactive le lazy-loading pour l'image la plus critique (souvent LCP).
 * Approche pragmatique : première image "importante" sur singular.
 */
add_filter('wp_lazy_loading_enabled', function ($default, $tag_name, $context) {

	if ($tag_name !== 'img') {
		return $default;
	}

	if (!is_singular()) {
		return $default;
	}

	// Contexte "the_content" et "wp_get_attachment_image" sont fréquents.
	// On ne peut pas identifier "la" LCP de façon parfaite côté PHP sans heuristiques.
	static $already_disabled_once = false;

	if (!$already_disabled_once && in_array($context, ['the_content', 'wp_get_attachment_image', 'post_thumbnail'], true)) {
		$already_disabled_once = true;
		return false; // Pas de lazy pour la première occurrence
	}

	return $default;
}, 10, 3);

2) Ajouter fetchpriority=high sur l’image critique

Les navigateurs modernes utilisent fetchpriority. WordPress peut déjà gérer des attributs d’image via filtres. Ici, on ajoute fetchpriority="high" à la featured image sur singular si elle est en haut de page (heuristique).

<?php
/**
 * Ajoute fetchpriority=high sur la miniature mise en avant en haut de page.
 * Attention : ne le faites pas sur 10 images, sinon vous cassez la priorisation.
 */
add_filter('wp_get_attachment_image_attributes', function ($attr, $attachment, $size) {

	if (!is_singular()) {
		return $attr;
	}

	// On cible uniquement l'image mise en avant si le thème utilise post-thumbnail
	// Heuristique : classe "wp-post-image" ajoutée par the_post_thumbnail()
	$class = isset($attr['class']) ? (string) $attr['class'] : '';
	if (str_contains($class, 'wp-post-image')) {
		$attr['fetchpriority'] = 'high';
		// Bonus : si vous voyez des CLS, assurez-vous que width/height existent
	}

	return $attr;
}, 10, 3);

3) Preload ciblé (sans tomber dans le preload “partout”)

Le preload d’une image LCP peut aider, mais seulement si vous préchargez la bonne variante. Précharger une URL “large desktop” sur mobile est un anti-pattern : vous forcez le téléchargement d’une ressource trop grosse.

Approche raisonnable : précharger l’image mise en avant en utilisant la source la plus probable pour mobile (ex: 768w). On va récupérer une URL de taille intermédiaire, pas l’originale.

<?php
/**
 * Preload raisonnable de l'image mise en avant en taille intermédiaire.
 * À adapter à votre design : choisissez une taille réellement utilisée au-dessus de la ligne de flottaison.
 */
add_action('wp_head', function () {

	if (!is_singular() || !has_post_thumbnail()) {
		return;
	}

	$thumb_id = get_post_thumbnail_id();
	if (!$thumb_id) {
		return;
	}

	// Choisissez une taille existante. 'medium_large' est souvent pertinente pour mobile.
	$src = wp_get_attachment_image_src($thumb_id, 'medium_large');
	if (!$src || empty($src[0])) {
		return;
	}

	$href = esc_url($src[0]);

	echo '<link rel="preload" as="image" href="' . $href . '">' . "n";
}, 1);

Note : si votre site utilise un plugin d’images qui génère des variantes à la volée (CDN avec paramètres), cette approche peut être contre-productive. Dans ce cas, mieux vaut laisser le navigateur choisir via srcset et se concentrer sur sizes et fetchpriority.


Étape 4 : WebP/AVIF et qualité JPEG sans casser le srcset

Objectif

Sur mobile, réduire les octets est souvent plus rentable que “micro-optimiser” une requête PHP. WebP/AVIF + une compression correcte font une différence immédiate, à condition de ne pas casser les URLs du srcset.

1) Ajuster la qualité de compression (JPEG/WebP) côté WordPress

WordPress expose des filtres pour la qualité. Beaucoup de sites traînent encore un snippet d’ancien tutoriel qui force 100 “pour la qualité”, et c’est une catastrophe sur mobile.

<?php
/**
 * Qualité d'images : compromis réaliste pour blog.
 * Attention : baisser trop peut dégrader les visuels (banding).
 */
add_filter('jpeg_quality', function ($quality) {
	return 82;
});

add_filter('wp_editor_set_quality', function ($quality, $mime_type) {
	// MIME types possibles : image/jpeg, image/webp, image/avif...
	return match ($mime_type) {
		'image/jpeg' => 82,
		'image/webp' => 80,
		'image/avif' => 45,
		default      => $quality,
	};
}, 10, 2);

2) Vérifier que vos tailles existent réellement (sinon srcset est pauvre)

Si votre thème n’enregistre que deux tailles, votre srcset sera limité. Ajoutez des tailles intermédiaires cohérentes avec vos breakpoints (sans en créer 25 : ça gonfle le stockage et les temps de régénération).

<?php
/**
 * Déclare quelques tailles utiles pour mobile/tablette.
 * À placer dans functions.php d'un thème enfant ou dans un plugin.
 */
add_action('after_setup_theme', function () {
	add_image_size('card_360', 360, 0, false);   // Cartes mobile
	add_image_size('card_540', 540, 0, false);   // Cartes mobile dense / tablette
	add_image_size('hero_960', 960, 0, false);   // Hero mobile/tablette
});

3) Utiliser ces tailles dans vos templates (sinon elles ne servent à rien)

<?php
// Exemple : grille d'articles (archive)
if (has_post_thumbnail()) {
	echo get_the_post_thumbnail(
		get_the_ID(),
		'card_360',
		[
			'class' => 'archive-card-thumb',
			'alt'   => the_title_attribute(['echo' => false]),
		]
	);
}

Compatibilité plugins d’optimisation d’images

Beaucoup de plugins/CDN remplacent src par une URL proxy et tentent de reconstruire srcset. C’est là que vous voyez des srcset incohérents (largeurs fausses, URLs 404, ou toutes les entrées identiques). Quand vous suspectez ça, désactivez temporairement le plugin et comparez le HTML.

Si vous devez absolument réécrire des URLs, faites-le via les filtres WordPress sur les sources (et testez le srcset complet), pas via un str_replace sur le buffer HTML.


Étape 5 : Lazy-loading sans dégrader le LCP (et les carrousels)

Le problème : sliders et images “au-dessus de la ligne de flottaison”

Sur Divi/Elementor/Avada, les carrousels et héro sections sont souvent rendus via JS. Si le lazy-loading est appliqué au mauvais endroit, vous obtenez :

  • un LCP tardif (image visible mais pas chargée) ;
  • un flash (placeholder) ;
  • un CLS si les dimensions ne sont pas fixées.

1) Forcer des dimensions (évite CLS)

WordPress ajoute généralement width/height si vous utilisez les fonctions natives. Si votre builder injecte un <img> sans dimensions, vous pouvez au moins corriger certains cas via wp_img_tag_add_width_and_height_attr (filtre) ou en remplaçant le rendu par un shortcode qui appelle wp_get_attachment_image().

Exemple de shortcode “image par ID” utilisable dans Elementor/Divi/Avada :

<?php
/**
 * Shortcode [img_id id="123" size="card_360" class="x"]
 * Objectif : produire un <img> WP natif (srcset/sizes/dimensions).
 */
add_shortcode('img_id', function ($atts) {
	$atts = shortcode_atts([
		'id'    => 0,
		'size'  => 'medium',
		'class' => '',
		'alt'   => '',
	], $atts, 'img_id');

	$id = (int) $atts['id'];
	if (!$id) {
		return '';
	}

	$alt = $atts['alt'] !== '' ? (string) $atts['alt'] : (get_post_meta($id, '_wp_attachment_image_alt', true) ?: '');

	return wp_get_attachment_image(
		$id,
		sanitize_key($atts['size']),
		false,
		[
			'class' => sanitize_html_class($atts['class']),
			'alt'   => $alt,
		]
	);
});

2) Ne pas “defer/async” n’importe quel script d’images

Certains plugins ajoutent un script de lazy-load custom et le passent en defer. Si ce script est responsable de remplacer data-src en src, le defer peut retarder l’affichage. Si vous devez différer, faites-le uniquement sur les scripts non critiques, et testez le LCP.

<?php
/**
 * Exemple : defer uniquement sur un script non critique.
 * Ne defer pas un script qui hydrate vos images au-dessus de la ligne de flottaison.
 */
add_filter('script_loader_tag', function ($tag, $handle, $src) {

	$non_critical = [
		'contact-form-7', // exemple
	];

	if (in_array($handle, $non_critical, true)) {
		return '<script src="' . esc_url($src) . '" defer></script>';
	}

	return $tag;
}, 10, 3);

Configuration serveur

.htaccess (Apache) : cache navigateur agressif pour les images

Si vos URLs d’images sont “fingerprintées” (WordPress ajoute des suffixes de taille, et beaucoup de CDN ajoutent des paramètres), vous pouvez mettre un cache long. Attention : si vous servez des images via un plugin qui réécrit tout sans versioning, un cache long peut retarder les mises à jour.

# .htaccess (Apache) - à placer dans le bloc approprié, hors règles WordPress
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/jpeg "access plus 1 year"
  ExpiresByType image/png  "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType image/avif "access plus 1 year"
</IfModule>

<IfModule mod_headers.c>
  <FilesMatch ".(jpe?g|png|webp|avif)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

php.ini : mémoire suffisante pour générer des tailles (sinon pas de srcset riche)

Quand la génération échoue (images lourdes, AVIF), vous finissez avec peu de tailles, donc un srcset médiocre. Un réglage minimal réaliste :

; php.ini (valeurs typiques)
memory_limit = 256M
max_execution_time = 120

wp-config.php : limiter l’édition d’images en prod

Si vous avez des auteurs qui recadrent/éditent souvent, WordPress génère de nouvelles versions. C’est normal, mais sur un hébergement limité, ça peut créer des lenteurs IO. Vous pouvez encadrer les usages (process), pas forcément le code. Côté config, gardez surtout un environnement stable et des logs.


Vérification des résultats

1) Vérifier le markup rendu (srcset/sizes/dimensions)

Ajoutez temporairement un check côté PHP sur une page précise (par slug), et logguez la présence des attributs pour l’image mise en avant.

<?php
add_action('wp', function () {
	if (!is_singular('post')) {
		return;
	}
	if (get_post_field('post_name', get_queried_object_id()) !== 'mon-article-test') {
		return;
	}
	if (!has_post_thumbnail()) {
		return;
	}

	$id = get_post_thumbnail_id();
	$html = wp_get_attachment_image($id, 'large', false, ['class' => 'test-thumb']);

	error_log('[perf-check] thumb html: ' . $html);
});

2) Vérifier les variantes réellement servies (sans capture d’écran)

Sur un environnement de test, vous pouvez utiliser un petit script Node ou curl pour comparer le Content-Length de différentes URLs du srcset. Exemple simple avec curl (à adapter) :

# Remplacez par une URL d'image réelle (une entrée du srcset)
curl -I "https://example.com/wp-content/uploads/2026/04/image-768x512.webp" | grep -i -E "content-length|content-type|cache-control"

3) Attendus (métriques)

  • LCP : baisse notable si l’image héro est correctement priorisée et dimensionnée.
  • CLS : doit tendre vers 0 si vos images ont width/height (ou ratio CSS stable).
  • Poids transféré : baisse forte sur mobile si sizes est corrigé.

Si les performances ne s’améliorent pas

1) Vérifier que votre code s’exécute vraiment

Erreur réaliste : le snippet est collé dans le mauvais fichier, ou dans un plugin de snippets désactivé sur certaines pages. Ajoutez un log explicite :

<?php
add_action('init', function () {
	if (!is_admin()) {
		error_log('[perf] snippets images responsive chargés');
	}
});

2) Conflit avec cache/minification

Autre classique : vous modifiez sizes, mais le cache page sert encore l’ancien HTML. Videz :

  • cache du plugin (page cache) ;
  • cache serveur (Varnish/NGINX microcache) ;
  • cache CDN ;
  • cache navigateur (ou test en fenêtre privée).

3) Un plugin réécrit le HTML après WordPress

Si votre srcset disparaît “après coup”, cherchez un buffer de sortie. Test simple : désactivez temporairement les plugins d’optimisation HTML/JS, et comparez la sortie.

4) Images en background CSS (aucun srcset possible)

Sur beaucoup de builders, le “hero” est un background-image. Là, srcset/sizes ne s’applique pas. Il faut passer à un vrai <img> (ou <picture>) ou gérer des media queries CSS avec plusieurs backgrounds. Si vous restez en CSS, au minimum :

/* Exemple : backgrounds responsives (moins optimal qu'un <img> mais mieux que rien) */
.hero {
  background-image: url('/wp-content/uploads/2026/04/hero-540.webp');
}
@media (min-width: 768px) {
  .hero {
    background-image: url('/wp-content/uploads/2026/04/hero-960.webp');
  }
}
@media (min-width: 1200px) {
  .hero {
    background-image: url('/wp-content/uploads/2026/04/hero-1600.webp');
  }
}

Pièges et erreurs courantes

Symptôme Cause probable Vérification Solution
Le mobile télécharge une image énorme (ex: 2000px) sizes trop large ou absent Inspecter le HTML : sizes annonce une largeur irréaliste Filtrer wp_calculate_image_sizes selon le layout réel
srcset absent partout Images codées en dur (URL), ou plugin/builder qui génère son propre markup Rechercher <img sans srcset dans le HTML Utiliser wp_get_attachment_image() / shortcode basé sur ID
CLS visible (sauts de mise en page) Images sans width/height, ou placeholders instables Logs (audit) + inspection du markup Rendu WP natif, ou fixer un ratio CSS stable
LCP pire après “optimisation lazy load” Lazy appliqué à l’image LCP Mesurer LCP et vérifier loading="lazy" sur l’image héro Désactiver lazy pour la première image critique via wp_lazy_loading_enabled + fetchpriority
Images floues sur mobile sizes trop petit (vous “mentez” au navigateur) Comparer largeur CSS réelle et valeur de sizes Ajuster sizes (vw/px) + prévoir tailles intermédiaires
Erreur fatale après ajout du snippet Parenthèse/point-virgule manquant, ou fonction appelée trop tôt Consulter wp-content/debug.log Corriger la syntaxe, placer le code dans un plugin ou thème enfant, utiliser le bon hook
Aucun effet malgré le code Cache page/CDN sert l’ancien HTML Tester en désactivant le cache, ou ajouter un marqueur dans le HTML Vider tous les caches, invalider CDN, retester

Erreurs que je vois souvent (et qui font perdre du temps)

  • Coller un filtre dans functions.php du thème parent (puis le perdre à la mise à jour).
  • Utiliser un hook inadapté (ex: modifier sizes via the_content avec des regex : fragile et lent).
  • Tester sur production sans sauvegarde ni rollback (un snippet cassé = écran blanc).
  • Garder un vieux snippet de 2018 qui force la qualité JPEG à 100.

Conseils de maintenance

  • Verrouillez vos tailles d’images : changez-les rarement, et documentez-les (sinon régénérations coûteuses).
  • Surveillez les builders/addons : après une mise à jour Divi/Elementor/Avada, vérifiez que le markup d’image n’a pas changé.
  • Évitez les réécritures HTML globales (output buffering “optimisation”) : c’est la source n°1 de srcset cassés.
  • Gardez un article “page test perf” avec une image héro, une grille et une sidebar. C’est votre canari après chaque update.

Ressources


FAQ

Est-ce que WordPress 6.9.4 génère automatiquement srcset et sizes ?

Oui, si vous rendez vos images via les fonctions natives (wp_get_attachment_image(), the_post_thumbnail(), images insérées dans l’éditeur). Si vous injectez une URL dans un <img> “fait main”, vous perdez la génération automatique.

Pourquoi j’ai un srcset mais le mobile prend quand même la grande image ?

Parce que sizes indique au navigateur une largeur d’affichage plus grande que la réalité. Le navigateur choisit alors une source plus large dans le srcset. Corrigez sizes via wp_calculate_image_sizes en respectant votre CSS.

Puis-je mettre sizes=”100vw” partout ?

Vous pouvez, mais c’est souvent faux sur les grilles, sidebars et mises en page en colonnes. 100vw force des sources plus grandes que nécessaire. Utilisez des breakpoints réalistes (48vw, 30vw, etc.).

Dois-je désactiver le lazy-loading pour améliorer la vitesse ?

Non. Désactivez-le uniquement pour l’image LCP (souvent la première image visible) et certains composants JS (sliders) si nécessaire. Le lazy-loading reste utile pour les images sous la ligne de flottaison.

fetchpriority=”high” suffit-il à améliorer le LCP ?

Ça aide, mais si l’image reste trop lourde (mauvais sizes, pas de bonne taille intermédiaire, format non optimisé), le LCP restera mauvais. Traitez d’abord la bonne ressource (taille/format), ensuite la priorité.

Les images en background CSS profitent-elles de srcset ?

Non. Vous devez utiliser des media queries CSS (plusieurs backgrounds) ou, mieux, remplacer par un vrai <img> (ou <picture>) pour laisser le navigateur choisir.

Mon plugin d’optimisation d’images remplace les URLs : est-ce un problème ?

Ça peut l’être si le plugin casse le srcset (URLs invalides, largeurs incohérentes, mêmes URLs répétées). Testez en le désactivant temporairement et comparez le HTML rendu.

Où placer ces snippets proprement ?

Le plus fiable : un petit plugin (ou mu-plugin) versionné. Évitez le thème parent. Un thème enfant est acceptable, mais vous risquez de mélanger performance et présentation.

Que faire si je change mes tailles d’images après coup ?

Vous devrez régénérer les miniatures pour que le srcset propose les nouvelles tailles. Faites-le en environnement de staging, puis planifiez en prod (batch, hors heures de pointe), surtout si vous avez une grosse médiathèque.

Comment savoir quelle image est le LCP sans outil graphique ?

Sans outil navigateur, c’est difficile. Côté code, vous pouvez appliquer une heuristique (featured image / première image du contenu) et vérifier l’impact sur vos mesures. Pour être certain, utilisez les métriques terrain (RUM) ou les rapports de performance de votre stack, mais ça sort du périmètre “code uniquement” ici.