Lazy loading et hooks PrestaShop : les pièges qui cassent le SEO

Lazy loading et hooks PrestaShop : les pièges qui cassent le SEO
Lazy loading mal configuré, hooks qui injectent du contenu en AJAX, animations qui masquent du texte : ces patterns courants dans PrestaShop créent des angles morts invisibles pour Googlebot. Le guide pour les éviter.

Le lazy loading et les hooks PrestaShop sont deux leviers puissants pour optimiser les performances — mais mal implémentés, ils deviennent des pièges SEO redoutables. Images non indexées, contenu invisible aux crawlers, LCP dégradé : voici les erreurs à ne pas commettre et les patterns qui fonctionnent.

Pourquoi le lazy loading pose problème aux moteurs de recherche

Le lazy loading consiste à différer le chargement des ressources jusqu'à ce qu'elles soient proches du viewport. C'est une excellente pratique pour la performance... à condition de distinguer ce que Google peut crawler de ce qu'il ne peut pas.

Googlebot exécute JavaScript, mais avec des limites importantes :

  • Le rendu est asynchrone et peut intervenir plusieurs jours après la découverte de l'URL
  • Le viewport simulé est étroit (~1024px de large, scroll limité)
  • Les ressources lazy-loadées hors viewport initial peuvent ne jamais être chargées
  • Les images avec loading="lazy" trop tôt dans la page pénalisent le LCP

L'attribut native vs JavaScript lazy loading

PrestaShop 8 utilise l'attribut HTML natif loading="lazy" sur les images. C'est la solution recommandée — mais elle comporte un piège critique :

<!-- ❌ Mauvais : appliqué à l'image LCP -->
{block name="product_images"}
  <img src="{$product.cover.bySize.large_default.url}"
       loading="lazy"
       alt="{$product.name}"
       width="800" height="600">
{/block}

<!-- ✅ Correct : LCP sans lazy, reste en lazy -->
{block name="product_images"}
  <img src="{$product.cover.bySize.large_default.url}"
       fetchpriority="high"
       alt="{$product.name}"
       width="800" height="600">
  {foreach from=$product.images item=image name=images}
    {if !$smarty.foreach.images.first}
      <img src="{$image.bySize.large_default.url}"
           loading="lazy"
           alt="{$product.name}"
           width="800" height="600">
    {/if}
  {/foreach}
{/block}

La règle est simple : jamais de loading="lazy" sur l'image qui sera le LCP. En général, c'est l'image principale de la fiche produit ou le premier visuel d'une catégorie.

Les hooks PrestaShop et leur impact sur le rendu

Le système de hooks de PrestaShop permet d'injecter du contenu à des points précis du cycle de vie. Mais quand un module accroche un hook pour charger du contenu en JavaScript asynchrone, il crée un contenu invisible au moment du rendu initial.

Hook displayProductAdditionalInfo : le piège classique

Ce hook est massivement utilisé pour injecter des blocs "Vous aimerez aussi", des avis clients, ou des widgets de réassurance. Si le module charge son contenu via une requête AJAX post-rendu, Google ne verra jamais ce contenu lors du premier crawl.

// ❌ Module qui charge via AJAX après rendu
// Dans le JS du module :
$(document).ready(function() {
  $.ajax({
    url: '/module/mymodule/ajax',
    success: function(data) {
      $('#product-additional-info').html(data);
    }
  });
});
// ✅ Rendu côté serveur dans le hook PHP
public function hookDisplayProductAdditionalInfo(array $params): string
{
    $product = $params['product'];
    $relatedProducts = $this->getRelatedProducts((int) $product['id_product']);

    $this->context->smarty->assign([
        'related_products' => $relatedProducts,
    ]);

    return $this->display(__FILE__, 'views/templates/hook/related-products.tpl');
}

La différence est fondamentale : la première approche génère du contenu invisible à Googlebot lors du rendu initial, la seconde l'inclut directement dans le HTML retourné par le serveur.

Intersection Observer : bon outil, mauvais usage fréquent

De nombreux thèmes et modules utilisent l'Intersection Observer API pour déclencher des animations ou charger du contenu au scroll. Le problème : si du texte, des titres ou des liens sont marqués comme invisibles jusqu'à l'entrée dans le viewport, ils peuvent être ignorés par Googlebot.

// ❌ Contenu masqué jusqu'au scroll (invisible pour Googlebot)
.feature-block {
  opacity: 0;
  transform: translateY(30px);
  transition: all 0.6s ease;
}

.feature-block.visible {
  opacity: 1;
  transform: translateY(0);
}
// ✅ Contenu visible par défaut, animation en overlay
.feature-block {
  opacity: 1; /* Visible par défaut */
  transform: none;
}

/* L'animation est ajoutée uniquement si JS est disponible */
.js-loaded .feature-block:not(.visible) {
  opacity: 0;
  transform: translateY(30px);
  transition: all 0.6s ease;
}

Ajoutez la classe js-loaded sur le <body> via JavaScript au chargement. Ainsi, sans JS (comme lors d'un crawl rapide sans rendu complet), tout le contenu reste visible.

Override de displayHeader : attention aux scripts bloquants

Un pattern courant dans les modules PrestaShop est d'utiliser hookDisplayHeader pour injecter des scripts JavaScript. Si ces scripts sont render-blocking, ils retardent le First Contentful Paint et donc le LCP.

// ❌ Script synchrone injecté dans le header
public function hookDisplayHeader(): string
{
    return '<script src="' . $this->getPathUri() . 'views/js/widget.js"></script>';
}
// ✅ Utiliser registerJavascript avec les bons paramètres
public function hookActionFrontControllerSetMedia(): void
{
    $this->context->controller->registerJavascript(
        'my-module-widget',
        'modules/' . $this->name . '/views/js/widget.js',
        [
            'position' => 'bottom',
            'priority' => 200,
            'attributes' => 'defer',
        ]
    );
}

Utiliser registerJavascript avec 'attributes' => 'defer' et 'position' => 'bottom' garantit que le script ne bloque pas le rendu. PrestaShop gère également la concaténation et la minification des assets.

Structured data et hooks : le contenu qui disparaît

Les rich snippets (avis, prix, disponibilité) sont souvent injectés via des hooks. Si le JSON-LD est généré côté client après rendu, Google peut rater les données structurées et ne pas afficher les rich snippets dans les SERP.

// ✅ JSON-LD généré côté serveur dans hookDisplayHeader
public function hookDisplayHeader(array $params): string
{
    if (!$this->context->controller instanceof ProductController) {
        return '';
    }

    $product = $this->context->controller->getTemplateVarProduct();

    $structuredData = [
        '@context' => 'https://schema.org/',
        '@type' => 'Product',
        'name' => $product['name'],
        'offers' => [
            '@type' => 'Offer',
            'price' => $product['price'],
            'priceCurrency' => $this->context->currency->iso_code,
            'availability' => $product['quantity'] > 0
                ? 'https://schema.org/InStock'
                : 'https://schema.org/OutOfStock',
        ],
    ];

    return '<script type="application/ld+json">'
        . json_encode($structuredData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        . '</script>';
}

Checklist : hooks et lazy loading compatibles SEO

  • Image LCP : jamais de loading="lazy", ajouter fetchpriority="high"
  • Contenu textuel critique : toujours rendu côté serveur, pas en AJAX
  • Animations d'entrée : contenu visible par défaut, CSS conditionnel à .js-loaded
  • Scripts de modules : registerJavascript avec defer et position bottom
  • JSON-LD : injecté dans hookDisplayHeader côté serveur
  • Hooks de contenu : rendu Smarty côté serveur, pas de fetch JS post-rendu

Vérifier avec les outils Google

Avant de mettre en production, testez le rendu réel avec ces outils :

  • Google Search Console → Inspection d'URL : voir le rendu HTML tel que Googlebot le voit
  • PageSpeed Insights : identifier l'élément LCP et vérifier qu'il n'a pas loading="lazy"
  • Chrome DevTools → Lighthouse : audit "Offscreen images" pour valider le lazy loading

La règle d'or : tout contenu qui doit être indexé doit être présent dans le HTML retourné par le serveur. Le JavaScript côté client est un bonus pour l'expérience utilisateur, pas un vecteur de contenu SEO.

Conclusion

Lazy loading et hooks PrestaShop sont des outils puissants, mais leur mauvaise utilisation crée des angles morts que Googlebot ne peut pas combler. L'image LCP sans fetchpriority, le contenu injecté en AJAX, les animations qui masquent du texte : chacun de ces patterns coûte des positions dans les SERP sans que vous vous en rendiez compte immédiatement.

Auditez vos modules avec l'outil d'inspection d'URL de la Search Console : comparez le HTML rendu avec la source — la différence vous dira exactement ce que Googlebot ne voit pas.

Besoin d'un audit de rendu ou d'une revue de vos hooks PrestaShop ? Contactez-moi pour en discuter !

Jonathan Le-Peru

Écrit par Jonathan Le-Peru

Développeur backend avec plus de 7 ans d'expérience, spécialisé dans la création de solutions e-commerce robustes avec Prestashop. Passionné par l'optimisation des performances et les bonnes pratiques de développement.