LiteSpeed Cache : lazy‑loading, LQIP et VPI pour optimiser les images

Optimiser les images PrestaShop avec LiteSpeed : lazy‑loading sans nuire au LCP, LQIP efficace, VPI pour prioriser les visuels et validation via CrUX/WebPageTest.

Écran d'ordinateur affichant du code et des icônes liées à l'optimisation d'images avec LiteSpeed Cache.

Table des matières :

  1. LiteSpeed Cache côté serveur : accélérer le HTML ne résout pas (seul) le problème des images
  2. Lazy-loading : implémentation robuste et erreurs classiques sur PrestaShop
  3. LQIP (Low Quality Image Placeholder) : utile, mais coûteux si mal exécuté
  4. VPI (Viewport Images) : prioriser les images au-dessus de la ligne de flottaison
  5. Mesurer, valider, et éviter les faux gains (Lighthouse, CrUX, RUM)

LiteSpeed Cache côté serveur : accélérer le HTML ne résout pas (seul) le problème des images

Sur un stack e-commerce, « LiteSpeed Cache » recouvre deux réalités : LSCache (le cache serveur intégré à LiteSpeed Web Server / OpenLiteSpeed) et les fonctionnalités front associées dans l’écosystème LiteSpeed (souvent exposées via des plugins CMS). Côté PrestaShop, vous bénéficiez surtout de LSCache au niveau HTTP (cache page, cache objets éventuel, compression, HTTP/3), mais il n’existe pas de support « core » équivalent à WordPress pour activer LQIP/VPI en un bouton. Traduction : le cache réduit le TTFB, mais si vos pages listent 40–200 images non optimisées, vous resterez plafonné par le poids total, la priorisation réseau et le comportement du navigateur.

Ce point est souvent mal compris : en e-commerce, une page « rapide » côté serveur peut rester « lente » côté utilisateur, parce que le navigateur passe ensuite l’essentiel du temps à :

  • résoudre les DNS/TLS vers votre domaine images/CDN,
  • télécharger des dizaines de miniatures,
  • décoder et rasteriser des images trop grandes (CPU mobile),
  • gérer la concurrence réseau (priorités, limites de connexions, HTTP/2/3).

Le point non négociable : LSCache n’est réellement efficace sur PrestaShop que si vous avez une stratégie claire pour les pages anonymes (catégories, fiches produits, CMS) et une exclusion stricte des parcours session/cart/checkout. PrestaShop s’appuie lourdement sur les cookies (session, panier, devises, langue), et le cœur n’implémente pas nativement un « hole punching » type ESI pour rendre certains blocs dynamiques cacheables. En pratique, si vous forcez un cache full-page sans varier/contourner ces cookies, vous finissez avec des bugs de paniers « partagés » ou de prix erronés. L’approche réaliste : cache agressif pour les pages publiques, puis composants dynamiques chargés en AJAX (mini-panier, header) si nécessaire.

Pour vérifier que votre couche LiteSpeed est correctement en place, partez de mesures basiques avant de toucher aux images : (1) header de cache (X-LiteSpeed-Cache: hit/miss selon configuration), (2) variation desktop/mobile si vous avez des templates distincts, (3) cohérence des cookies de contournement (connexion, panier, préférences). Ensuite seulement, vous attaquez l’optimisation images.

Un mini-checklist « cache OK » (utile en recette) :

  • Une page catégorie publique : 2 rafraîchissements → vous devez voir un hit (à partir du 2e chargement).
  • Un utilisateur connecté : cache désactivé ou privé selon votre stratégie, mais surtout pas de contenu partagé.
  • Changement de devise / langue : vérifiez que le HTML et les prix varient correctement (sinon votre clé de cache est trop « large »).
  • Cache statique images : vérifiez des Cache-Control longs sur /img/ (et côté CDN si vous en avez un), sinon chaque navigation revalide inutilement.

Pour une vue plus large sur l’arbitrage cache/CDN/Varnish lors de pics de trafic, le retour d’expérience dans PrestaShop soldes : optimiser caches, CDN et Varnish pour trafic élevé donne un cadre concret sur ce qu’on peut (et ne peut pas) cacher en e-commerce.

Lazy-loading : implémentation robuste et erreurs classiques sur PrestaShop

Le lazy-loading moderne commence par le standard HTML. Sur MDN, l’attribut loading est défini simplement : « Indicates how the browser should load the image. » (MDN, documentation de <img> : loading, MDN — img element). Sur PrestaShop 8.1/9.x (PHP 8.2/8.3), ajouter loading="lazy" aux images de listings (catégories, recherche, cross-sell) est généralement un gain immédiat sur la charge initiale. Mais c’est aussi le piège le plus fréquent : ne pas distinguer les images candidates au LCP.

Le LCP (Largest Contentful Paint) est le metric qui prend le plus cher quand on « lazy-load tout ». Google résume la métrique ainsi : « Largest Contentful Paint (LCP) measures loading performance. To provide a good user experience, LCP should occur within 2.5 seconds of when the page first starts loading. » (web.dev, web.dev — LCP). Sur une fiche produit, l’image principale est souvent l’élément LCP : si elle est loading="lazy", vous retardez volontairement son téléchargement et dégradez le LCP (et parfois le rendu perçu). Règle opérationnelle : jamais de lazy-load sur l’image hero (PDP) ni sur les 1–2 premières images d’une grille au-dessus de la ligne de flottaison.

Erreurs classiques vues sur des thèmes/modèles PrestaShop :

  • Lazy-load sur le picture principal : certaines implémentations lazy-load « cassent » srcset (le navigateur n’a plus d’indices corrects et télécharge une mauvaise variante).
  • Images sans dimensions : vous gagnez un peu de réseau, mais vous perdez en stabilité visuelle (CLS) sur mobile.
  • Délégation au JS d’un module : plusieurs modules « lazy-load » ajoutent leurs propres wrappers et listeners → duplication, conflits, et parfois double téléchargement.
  • Background images (CSS) : loading="lazy" ne s’applique pas. Un hero en background-image peut devenir votre LCP… sans que vous ne le voyiez dans vos templates. Dans ce cas, passer l’image en <img> (ou <picture>) est souvent plus simple à prioriser.

Implémentation minimaliste (sans JS) côté thème Classic (Smarty). Exemple sur un listing produit (product.tpl ou product-miniature.tpl selon thème), en excluant les premiers items (VPI statique basique) :

{* PrestaShop 8.1 / thème Classic (Smarty) *}
{assign var=idx value=$smarty.foreach.products.iteration}
<img
  src="{$product.cover.bySize.home_default.url}"
  srcset="{$product.cover.bySize.home_default.url} 1x, {$product.cover.bySize.home_default_2x.url} 2x"
  sizes="(max-width: 768px) 50vw, 25vw"
  width="{$product.cover.bySize.home_default.width}"
  height="{$product.cover.bySize.home_default.height}"
  alt="{$product.cover.legend|escape:'htmlall':'UTF-8'}"
  loading="{if $idx <= 2}eager{else}lazy{/if}"
  decoding="async"
/>

Trois points à ne pas négliger : (1) width/height (ou aspect-ratio) pour éviter le CLS, (2) un srcset/sizes cohérent (sinon vous chargez trop gros sur mobile), (3) la compatibilité modules : beaucoup de modules injectent leurs propres images (badges, labels, sliders) ; si vous ne les traitez pas, la page reste pénalisée.

Une règle simple qui aide à garder la main : centralisez la décision de lazy-load dans le thème (ou dans un override unique) plutôt que de laisser 4 modules le faire chacun à leur façon. Sur un catalogue « classique », un compromis réaliste est :

  • PDP : image principale eager (et prioritaire), galerie en lazy (sauf 1–2 vignettes visibles).
  • Catégorie : 1ère rangée eager, le reste lazy, mais uniquement si vos miniatures sont correctement compressées (sinon la 1ère rangée plombe tout).
  • Homepage : attention aux sliders (souvent lourds). Ne rendez pas 5 slides « eager » : un seul visuel hero doit dominer.

LQIP (Low Quality Image Placeholder) : utile, mais coûteux si mal exécuté

Un LQIP est un placeholder très léger (souvent 200–1000 bytes en WebP/AVIF, ou une vignette base64) affiché immédiatement, remplacé ensuite par l’image finale. L’objectif n’est pas de « réduire le poids total » (vous téléchargez toujours l’image HD), mais de réduire le “blank screen” et de stabiliser le layout si votre CSS réserve l’espace. C’est exactement le type de feature que LiteSpeed Cache « plugin » sait automatiser sur certains CMS, mais sur PrestaShop vous devez le traiter comme une brique front (templates + JS) et une brique build/cron (génération des placeholders).

Quand LQIP vaut vraiment le coup (et quand éviter) :

  • Utile si : vos pages ont des grilles d’images, que l’utilisateur scrolle vite, et que la latence/CDN n’est pas parfaite (le LQIP masque le “flash” et rend le chargement plus fluide).
  • À éviter si : votre site est déjà lourd en JS (INP fragile) ou si vos images above-the-fold sont déjà très rapides (vous ajoutez complexité + risques de bugs pour peu de gain).

Côté génération, évitez le base64 inline par défaut : sur des catégories à 60 produits, vous gonflez le HTML et vous dégradez la compression si les placeholders ne se répètent pas. Préférez une stratégie « fichier » : pour chaque image id_image, générer xxx-lqip.webp (largeur 24–40 px) via ImageMagick ou sharp (Node.js) dans votre pipeline. Sur Linux, une commande typique ImageMagick ressemble à :

# Placeholder WebP minuscule et flouté (à adapter selon vos profils)
magick input.jpg -resize 32x32 -blur 0x8 -quality 35 output-lqip.webp

Sur PrestaShop, vous pouvez déclencher cette génération : (1) à l’import catalogue (hook de module), (2) en cron nocturne qui parcourt ps_image et vérifie la présence des dérivés, (3) lors de la régénération des miniatures. Attention : toucher au pipeline images de PrestaShop sans garde-fou peut déclencher des IO storms sur gros catalogues (NVMe conseillé).

Un garde-fou simple côté cron (sans entrer dans une « usine à gaz ») :

  • batch de N images (ex. 200–1000) par exécution,
  • limitation CPU (nice/ionice si besoin),
  • journaliser les échecs (images corrompues, droits, manque d’espace disque),
  • ne pas régénérer si le fichier LQIP existe déjà et semble valide (taille > 0, date cohérente).

Pour le diagnostic de lenteurs liées aux traitements et au SQL autour du catalogue, gardez sous la main Optimisation de code PrestaShop : diagnostiquer lenteurs et requêtes SQL.

Côté intégration, le pattern robuste consiste à servir le LQIP dans src, et l’URL finale dans data-src / data-srcset, avec un micro-script IntersectionObserver. Exemple (simplifié) :

<img
  class="lqip"
  src="/img/p/1/2/12-lqip.webp"
  data-src="/img/p/1/2/12-home_default.webp"
  data-srcset="/img/p/1/2/12-home_default.webp 1x, /img/p/1/2/12-home_default_2x.webp 2x"
  width="250" height="250"
  alt="..."
  loading="lazy"
  decoding="async"
/>
<noscript>
  <img src="/img/p/1/2/12-home_default.webp" width="250" height="250" alt="..." />
</noscript>

Le noscript est important : sans JS, vos visiteurs et certains outils SEO ne verront que le LQIP. Et oui, c’est un compromis : vous réintroduisez du JS (donc potentiel impact INP) pour un gain de rendu perçu. C’est à mesurer, pas à appliquer « parce que c’est disponible ».

Si vous cherchez une intégration « propre » côté UX, pensez aussi à la couche CSS : le LQIP doit être visuellement distinct (souvent flouté) et disparaître sans flash. Un pattern courant :

  • appliquer filter: blur(...) et/ou une légère désaturation au placeholder,
  • enlever le blur dès que l’image HD est chargée (classe .is-loaded),
  • conserver exactement les mêmes dimensions (sinon vous recréez du CLS).

VPI (Viewport Images) : prioriser les images au-dessus de la ligne de flottaison

VPI (Viewport Images) dans l’écosystème LiteSpeed correspond à une idée simple : ne pas lazy-loader ce qui est visible au chargement, et au contraire le rendre prioritaire. Le cœur du web perf moderne, c’est la priorisation : une page peut être « légère » mais mal priorisée, et donc lente sur LCP. Le navigateur doit comprendre quelles images sont critiques. Pour ça, deux leviers standards : (1) ne pas retarder ces images (loading="eager"), (2) pousser leur priorité (fetchpriority="high" ou rel=preload).

Sur MDN, fetchpriority est défini ainsi : « Provides a hint of the relative priority to use when fetching the image. » (MDN, <img> : fetchpriority, MDN — img element). En pratique sur PrestaShop : mettez fetchpriority="high" uniquement sur l’image LCP (souvent image principale produit) et éventuellement la bannière hero de homepage. Ne le mettez pas sur 10 images : vous cassez l’ordonnancement réseau.

Exemple PDP (Smarty) :

{* Image principale produit - candidate LCP *}
<img
  src="{$product.cover.large.url}"
  width="{$product.cover.large.width}"
  height="{$product.cover.large.height}"
  alt="{$product.cover.legend|escape:'htmlall':'UTF-8'}"
  loading="eager"
  fetchpriority="high"
  decoding="async"
/>

Le niveau au-dessus (quand ça vaut le coup) est le preload, utile si votre image LCP est découverte tard (ex. slider, picture complexe, ou CSS background que vous remplacez par un <img>). Avec <link rel="preload" as="image">, vous forcez le téléchargement tôt. Attention : un mauvais preload consomme de la bande passante critique (surtout sur mobile) et peut empirer LCP/INP. Si vous utilisez srcset, utilisez aussi imagesrcset / imagesizes dans le preload pour éviter de précharger une mauvaise variante.

Exemple (schématique) de preload compatible srcset (à placer dans le <head> du template) :

<link
  rel="preload"
  as="image"
  href="/img/p/1/2/12-large.webp"
  imagesrcset="/img/p/1/2/12-large.webp 800w, /img/p/1/2/[email protected] 1600w"
  imagesizes="(max-width: 768px) 100vw, 800px"
/>

Enfin, VPI n’est pas uniquement un sujet template : il dépend du CSS. Si votre above-the-fold réel sur mobile n’a rien à voir avec desktop (sticky header, bandeau promo, etc.), une règle « premiers 2 produits = eager » peut être fausse. Dans ce cas, la variante “propre” est de taguer explicitement les blocs critiques dans le thème (ex. data-vpi="true") et de n’exclure du lazy-loading que ces images-là.

Un moyen pragmatique de décider « quoi prioriser » sur PrestaShop (sans sur-optimiser) :

Type de page Image(s) généralement VPI Action recommandée
Fiche produit (PDP) image principale loading="eager" + fetchpriority="high" + dimensions fixes
Catégorie (PLP) 4–8 miniatures (selon grille) eager sur la 1ère ligne, reste en lazy
Homepage 1 hero (bannière) + éventuellement 1 visuel promo éviter les sliders « eager » multiples ; prioriser 1 seul visuel

Mesurer, valider, et éviter les faux gains (Lighthouse, CrUX, RUM)

Sans mesure, lazy-loading/LQIP/VPI, c’est de l’optimisation à l’aveugle. Votre pipeline minimal : (1) Lighthouse en CI ou sur une instance de préprod, (2) WebPageTest pour analyser la waterfall (priorités, préloads, concurrence), (3) données terrain via CrUX quand vous avez du trafic, et idéalement du RUM (Real User Monitoring) si vous pouvez instrumenter. Pour replacer ces métriques dans le contexte PrestaShop (catalogues volumineux, modules, surcharge JS), le guide PrestaShop performance : optimiser gros catalogues et Core Web Vitals détaille une méthodologie orientée goulots d’étranglement.

CrUX mérite une précision opérationnelle : c’est de la donnée « terrain » agrégée (sur une fenêtre glissante), donc parfaite pour valider une tendance après déploiement (ex. amélioration LCP mobile), mais moins adaptée pour diagnostiquer un bug précis apparu hier. La documentation officielle côté Chrome est un bon point d’entrée si vous ne l’avez jamais exploitée : Chrome — CrUX documentation

Concrètement, cherchez des signaux spécifiques : (a) LCP qui baisse après exclusion du lazy sur l’image hero + fetchpriority, (b) CLS qui baisse après ajout systématique width/height ou aspect-ratio, (c) nombre de requêtes images sur le chargement initial, (d) poids total transféré dans les 2 premières secondes (réseau 4G simulé). Un cas courant en PrestaShop : catégorie avec 48 miniatures à 120–180 KB chacune. Même avec lazy, si vos 8 images VPI sont trop lourdes, vous n’améliorez pas LCP. L’étape suivante est alors la compression + formats modernes (WebP/AVIF) et une politique srcset stricte.

Un « test de vérité » rapide dans WebPageTest/DevTools (sans changer une ligne de code) :

  • Comparez une PDP avec image principale en lazy vs eager + fetchpriority.
  • Regardez la waterfall : l’image LCP part-elle dans les toutes premières requêtes, ou attend-elle des CSS/JS ?
  • Vérifiez la taille réellement téléchargée (souvent, un srcset mal réglé fait charger une version desktop sur mobile).

Sur l’axe formats + SEO, l’article SEO PrestaShop : optimiser fiches produits, schema.org et images WebP est pertinent parce qu’il relie performance, indexation et rendu.

Dernier point : validez aussi la couche infrastructure. Si vous servez vos images via CDN, mesurez le TTFB et le cache HIT ratio au niveau edge (et le support HTTP/3 si disponible). En 2026, les gains « faciles » sur images viennent souvent de la latence et de la proximité réseau autant que du lazy-load. Pour choisir une stratégie CDN sans croyances, utilisez un comparatif orienté critères (formats, cache keys, purges, coûts egress) comme CDN en 2026 : comparatif Bunny.net, Cloudflare, Akamai, CloudFront, Fastly.

Et si vous êtes sur LiteSpeed/LSAPI, surveillez aussi la saturation côté PHP (WaitQ, workers) : un HTML plus rapide n’aide pas si le serveur est déjà en file d’attente — cf. Performances PHP LiteSpeed : cache opcode, LSAPI et suivi WaitQ.

Enfin, évitez les « faux gains » typiques :

  • Lighthouse local en desktop qui s’améliore, mais CrUX mobile qui stagne (souvent une histoire de priorités réseau et d’images trop lourdes).
  • LQIP qui rend « joli », mais qui ajoute une dette JS et complique l’accessibilité si les attributs alt/dimensions ne sont pas stricts.
  • Lazy-load qui réduit la charge initiale… mais augmente les téléchargements au scroll si le navigateur est forcé de reflow (images sans dimensions → CLS → mauvais ressenti).

L’objectif n’est pas d’activer des options, mais d’obtenir une chaîne cohérente : cache serveur sain (TTFB) + images correctement dimensionnées et compressées (poids) + priorisation explicite au-dessus de la ligne de flottaison (LCP) + mesure terrain (CrUX/RUM) pour valider que l’amélioration est réelle sur vos visiteurs.


À lire aussi