Performance

Imagem com loading=lazy no Magento 2 está matando o seu LCP

Lazy loading é ótimo para o que está fora da tela e péssimo para o que aparece primeiro. O Magento não faz essa distinção: ele carimba lazy em todas.

Por Roger Takemiya · Publicado em · 5 min de leitura

Você otimizou imagem, ligou WebP, pôs CDN, e o LCP da página de categoria continua em 3,4 s no PageSpeed. Abre o HTML da página, procura a primeira imagem de produto e está lá: loading="lazy".

Essa é a recomendação do próprio time do Chrome, escrita em letra grande: Don't lazy-load images that are likely to be in-viewport when the page loads, especially LCP images. O navegador só consegue decidir se uma imagem lazy vai carregar depois de calcular o layout — e é exatamente esse atraso que aparece na sua nota.

Onde o lazy está fixo no core

Não é configuração, não é o seu tema, não é extensão. Está escrito no template do Magento_Catalog:

vendor/magento/module-catalog/view/frontend/templates/product/image_with_borders.phtml

Dentro dele, a tag da imagem tem loading="lazy" literal, sem nenhuma condição. E não adianta trocar de template pelo layout: quem escolhe o arquivo é o ImageFactory, em PHP, com o caminho fixo no código. O image.phtml que existe ao lado está marcado no próprio comentário como deprecated template as image_with_borders is a primary one.

Esse template desenha toda imagem de produto renderizada por bloco: grade da categoria, resultado de busca, produtos relacionados, upsell, cross-sell, carrinho. Ou seja, em página de categoria, a sua imagem de LCP passa por ali.

O conserto: dois arquivos no seu tema

Copie o template para o seu tema, em app/design/frontend/Vendor/tema/Magento_Catalog/templates/product/image_with_borders.phtml, e faça o loading virar um valor que vem de fora, com lazy como padrão:

<?php
$attrs   = $block->getCustomAttributes();
$loading = $attrs['loading'] ?? 'lazy';
unset($attrs['loading']);
?>

Aí no <img> você troca o foreach para percorrer $attrs e a linha fixa por:

loading="<?= $escaper->escapeHtmlAttr($loading) ?>"

Agora copie também o product/list.phtml para o tema e conte os produtos, marcando os primeiros:

<?php $lcp = 0; ?>
<?php foreach ($_productCollection as $_product): ?>
    <?php
    $lcp++;
    $productImage = $block->getImage(
        $_product,
        $imageDisplayArea,
        $lcp <= 4 ? ['loading' => 'eager', 'fetchpriority' => 'high'] : []
    );
    ?>

O terceiro argumento do getImage() já existe no core e vai parar direto nos atributos da tag — o único que precisava do ajuste no template era o loading, porque ele estava chumbado.

Quantas imagens marcar? Quantas aparecem sem rolar a página no celular. Em grade de duas colunas, quatro é um bom número. Marcar demais é o erro oposto: se tudo é prioridade alta, nada é.

Não é invenção minha: o list.phtml do tema Hyvä faz exatamente isso, com uma variável eager_load_images_count que vale 3 por padrão e passa loading e fetchpriority nos primeiros produtos da coleção.

Na página do produto o problema é outro

Na página de produto, a imagem grande não sai desse template. Ela vem do product/view/gallery.phtml, num <img class="gallery-placeholder__image"> que o JS da galeria depois substitui.

Boa notícia: essa tag não tem loading="lazy". Má notícia: também não tem fetchpriority, e ela quase sempre é o LCP da página. Copie o template para o tema e acrescente uma linha:

<img
    alt="main product photo"
    class="gallery-placeholder__image"
    fetchpriority="high"
    src="<?= /* @noEscape */ $mainImageData ?>"

Sem isso, o navegador começa a baixar a imagem com prioridade baixa e só promove depois do layout — é o comportamento padrão para imagem, e o fetchpriority="high" existe justamente para pular essa espera.

Depois de mexer, o de sempre: setup:static-content:deploy e cache:flush. E meça de novo com dado de campo, não só com laboratório — LCP de listagem varia muito entre 4G e Wi-Fi.

Perguntas rápidas

Dá para resolver isso sem sobrescrever template?

Não de forma limpa. O atributo está escrito direto no HTML do template e o caminho do arquivo está fixo no PHP do ImageFactory. Plugin resolveria só se o valor viesse de um método, e não vem.

Hyvä tem o mesmo problema?

Não. O list.phtml do tema padrão do Hyvä já marca as primeiras imagens com loading eager e fetchpriority high, e o número vem de uma variável chamada eager_load_images_count, que vale 3 se você não mexer. Se a sua loja é Hyvä, esse ponto já está resolvido.

Vale a pena colocar preload da imagem no head em vez disso?

Em página de produto às vezes sim, porque a imagem é uma só. Em listagem não: você precisaria de um preload por imagem visível, e o fetchpriority na própria tag faz o mesmo trabalho com menos HTML.

Pra conferir na fonte

  1. image_with_borders.phtml no core do Magento — GitHub — magento/magento2
  2. ImageFactory.php (o template fixo em PHP) — GitHub — magento/magento2
  3. Browser-level image lazy loading — web.dev (Google)
  4. Optimize resource loading with the Fetch Priority API — web.dev (Google)

lcp core web vitals imagem

Precisa de um orçamento? Ficarei feliz em ajudar. Clique Aqui