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.phtmlDentro 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
- image_with_borders.phtml no core do Magento — GitHub — magento/magento2
- ImageFactory.php (o template fixo em PHP) — GitHub — magento/magento2
- Browser-level image lazy loading — web.dev (Google)
- Optimize resource loading with the Fetch Priority API — web.dev (Google)