Otimização de imagem no Next.js com next/image

Entenda por que uma tag img comum prejudica a performance do site, como o componente Image do Next.js resolve lazy loading, formato moderno e layout shift automaticamente, e o erro comum de esquecer width e height.


Já falei aqui sobre as camadas de cache do Next.js e sobre como medir a performance do seu site com Lighthouse. Imagem costuma ser o maior peso individual numa página, frequentemente responsável por boa parte do total de dado que o navegador precisa baixar, e o Next tem um componente específico pra resolver isso, que passa despercebido por quem só troca <img> por <Image> sem entender o que realmente muda por trás.

O problema de uma tag img comum

<img src="/banner.jpg" alt="Banner" />

Essa tag simples carrega o arquivo original, no tamanho original, não importa o quão pequeno ele apareça na tela. Uma imagem de 3000 pixels de largura, mostrada numa área de 400 pixels no celular, ainda assim baixa os 3000 pixels inteiros, desperdiçando dado, tempo de carregamento e, em conexão mais lenta, uma experiência ruim de verdade.

Além disso, uma <img> comum carrega assim que a página processa aquela linha do HTML, mesmo que a imagem esteja bem lá embaixo, fora da área visível inicial da tela. Isso compete por banda com o conteúdo que o usuário realmente precisa ver primeiro.

O que o componente Image resolve

import Image from "next/image";
 
export function Banner() {
  return <Image src="/banner.jpg" alt="Banner" width={800} height={400} />;
}

Por trás dessa troca simples de tag, o Next passa a gerar automaticamente várias versões da mesma imagem, em tamanhos diferentes, e serve a versão mais próxima do espaço real que ela ocupa na tela do dispositivo que está acessando. Um celular recebe uma versão bem menor do que um monitor grande, sem você precisar gerar esses tamanhos manualmente.

Lazy loading automático

Por padrão, o componente Image só carrega a imagem quando ela está prestes a entrar na área visível da tela, exatamente o comportamento que uma <img> comum não tem sem esforço extra. Isso significa que uma página com muitas imagens, a maioria fora da primeira tela, só baixa de fato as que o usuário está perto de ver, economizando dado pra quem nunca rola até o final da página.

Pra imagem que já aparece assim que a página carrega, tipo um banner logo no topo, esse comportamento padrão é ruim, porque atrasa desnecessariamente o carregamento de algo que já deveria estar visível de cara. Pra esses casos existe a propriedade priority:

<Image src="/banner.jpg" alt="Banner" width={800} height={400} priority />

Isso avisa o Next pra carregar essa imagem imediatamente, sem esperar o comportamento de lazy loading padrão, priorizando ela desde o primeiro momento.

Formato moderno, gerado sob demanda

Formatos como WebP e AVIF comprimem melhor que JPEG ou PNG tradicional, entregando qualidade parecida com um arquivo bem menor. O componente Image detecta o que o navegador do visitante suporta e serve o formato mais moderno disponível automaticamente, sem você precisar converter e manter várias versões do mesmo arquivo original manualmente.

O erro comum: esquecer width e height

// isso quebra a otimização de layout
<Image src="/banner.jpg" alt="Banner" fill />

A propriedade width e height não serve só pra definir o tamanho visual, ela existe principalmente pra o navegador já reservar o espaço exato daquela imagem na página, antes mesmo dela terminar de carregar. Sem essa reserva de espaço, o conteúdo ao redor da imagem se desloca assim que ela aparece, um problema conhecido como layout shift, que o próprio Lighthouse mede e penaliza como Cumulative Layout Shift.

Quando o tamanho final realmente não é conhecido de antemão (por exemplo, uma imagem que precisa preencher um contêiner de tamanho variável), a alternativa correta é usar fill junto de um contêiner pai com posição definida via CSS, não simplesmente omitir width e height:

<div style={{ position: "relative", width: "100%", height: "300px" }}>
  <Image src="/banner.jpg" alt="Banner" fill style={{ objectFit: "cover" }} />
</div>

Aqui o espaço reservado vem do próprio contêiner, então o layout continua estável mesmo sem dimensão fixa na imagem.

Fechando

Trocar <img> por <Image> no Next não é sobre seguir uma convenção do framework por seguir, é sobre resolver três problemas reais de uma vez: baixar imagem no tamanho certo pra cada tela, adiar o carregamento do que ainda não está visível, e reservar o espaço certo pra evitar que a página pule quando a imagem termina de carregar. O único cuidado que quebra parte desse ganho é esquecer width e height (ou o fill com contêiner posicionado), justamente a parte que garante que o layout não trema durante o carregamento.

Direto na sua
caixa de entrada.

Um aviso por e-mail sempre que eu publicar um post novo. Sem spam, sem newsletter chata, só isso.