next/image e SEO: o que o componente não resolve
Publicado em
O next/image resolve o lado de performance da imagem — formato moderno,
srcset, dimensões reservadas contra CLS — mas não resolve nada de
indexação: alt, contexto textual em volta, nome de arquivo e presença no
sitemap continuam por sua conta, e o componente é cego para imagem declarada
em CSS. A SERP inteira trata "SEO de imagem" como sinônimo de "performance
de imagem"; este artigo é sobre a metade que o componente não toca — que é
justamente a metade que decide se a imagem existe para o índice.
O que o next/image entrega de graça
O consenso, em um parágrafo: o componente serve o formato mais leve que o
browser aceita, gera o srcset para cada densidade e viewport, reserva o
espaço da imagem no layout (com width/height obrigatórios) e aplica lazy
loading por padrão. A configuração deste site cobre AVIF e WebP com os
breakpoints ajustados ao layout:
// next.config.ts
// ── Otimização de imagens ──────────────────────────────────────────────────
images: {
// Formatos modernos em ordem de preferência (avif < webp < original)
formats: ['image/avif', 'image/webp'],
// Breakpoints para srcset responsivo
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},O resultado prático desse arranjo aparece no baseline de 2026-07-20 deste site: CLS 0.000 nas 10 páginas. Com dimensões explícitas em toda imagem, o deslocamento de layout causado por imagem simplesmente não existe. Isso é o que o componente compra. Agora, o que ele não compra.
O que ele não entrega (e ninguém avisa)
O next/image não escreve alt, não coloca a imagem no sitemap, não dá nome
semântico ao arquivo, não cria contexto textual em volta da imagem — e não
enxerga background-image de CSS. Nenhum desses itens é detalhe: são
exatamente os sinais que o Google usa para entender e indexar uma imagem. O
componente otimiza a entrega do arquivo; a indexação do que o
arquivo mostra é outra disciplina, e ela não vem no pacote.
O espelho do diagrama:
| Resolvido pelo componente | Sua responsabilidade |
|---|---|
| formato moderno (AVIF/WebP) | alt descritivo, no idioma da página |
srcset por viewport e densidade | contexto textual em volta da imagem |
| lazy loading por padrão | nome de arquivo semântico |
| dimensões reservadas (CLS) | presença no sitemap de imagens |
Alt: a única parte de indexação de imagem que é 100% sua
O alt é obrigatório no componente — o TypeScript reclama se faltar — mas
obrigatório não é sinônimo de bom. Alt bom descreve o que a imagem mostra
para quem não a vê, sem keyword stuffing: "Relatório do Search Console com o
status 'Rastreada, mas não indexada' em destaque", não "seo nextjs search
console indexação".
Decorativa leva alt vazio, não alt bonito
Imagem decorativa — fundo, ornamento, duplicata visual — leva um alt
vazio. O atributo presente e vazio diz a leitores de tela e crawlers:
ignore. A seção do caso real, no fim do artigo, mostra o componente deste
site em que isso importa na prática: a cópia borrada de fundo leva alt
vazio e aria-hidden, e a descrição mora só na imagem de conteúdo.
Alt no idioma da página
Regra deste repositório, não opinião: alt em pt-BR nas páginas pt-BR. O alt participa da relevância textual da página — um alt em inglês num artigo em português é um sinal dissonante, além de inútil para o leitor de tela do público real da página.
sizes: o atributo que decide qual arquivo o usuário baixa
O srcset dá as opções; o sizes diz ao browser qual escolher antes do
layout existir. Sem ele, imagens responsivas assumem que a imagem ocupa 100vw
— e um card de 400px baixa o arquivo de 1080. O formato é uma media query
por regra de layout:
<Image
src="/images/blog/exemplo/diagrama.webp"
alt="…"
width={1200}
height={675}
sizes="(max-width: 768px) 100vw, 720px"
/>Lido de trás pra frente: em telas grandes a imagem renderiza a 720px, então
baixe a variante mais próxima disso; abaixo de 768px ela ocupa a viewport
inteira. Errar o sizes não quebra nada visível — só desperdiça bytes
silenciosamente, e é por isso que quase ninguém corrige.
preload: uma bala, e por que este site não gastou nenhuma
Aqui a SERP em pt-BR está quase toda desatualizada, então vale marcar a
versão: a partir do Next.js 16, a prop priority foi deprecada em favor
de preload — a tipagem do componente marca priority como deprecated e
aponta para a nova prop. A documentação recomenda, para a maioria dos casos,
loading="eager" ou fetchPriority="high" em vez do preload completo. O
efeito do preload é o pacote todo: desliga o lazy, emite
<link rel="preload"> no head e sinaliza prioridade alta de fetch.
A regra de uso não mudou com o nome: uma bala por página, gasta apenas no elemento LCP quando ele é uma imagem. Priorização funciona por contraste — marcar três imagens como prioritárias é dizer ao browser que nada é prioritário.
E a razão de este site não ter gastado nenhuma: o LCP daqui é texto, em todas as páginas medidas. Não há hero image; as imagens de conteúdo ficam abaixo da dobra e devem permanecer lazy. O comentário no override de MDX deste repositório existe para ninguém "otimizar" isso por engano:
// src/components/mdx/mdx-components.tsx
// ─────────────────────────────────────────────────────────────────────────────
// Componentes disponíveis dentro dos arquivos MDX de /content.
//
// `Image` (next/image) é a forma sancionada de embutir imagens em artigos:
// exige width/height explícitos (sem CLS) e serve formato otimizado (§6).
// Imagens de conteúdo ficam abaixo da dobra — nunca usar `preload` aqui.
// ─────────────────────────────────────────────────────────────────────────────Não vou fingir um antes/depois de hero image que este site não tem. Se o seu LCP é uma imagem, o guia de melhoria de LCP cobre o mecanismo completo — descoberta, prioridade e o custo de cada milissegundo de atraso.
O ponto cego: background-image em CSS
O next/image renderiza <img>. Imagem declarada em CSS
(background-image) fica fora de tudo: fora do pipeline de otimização, fora
do srcset, invisível para o preload scanner do browser — e invisível para
o Google Imagens, que não indexa imagem carregada via CSS. Se a imagem é
conteúdo (produto, capa, diagrama), ela precisa ser uma tag <img> no HTML.
O estrago que o background-image faz especificamente no LCP — descoberta
tardia, o scanner que não a enxerga — está detalhado no catálogo de
antipadrões de LCP; aqui fica só o veredito de
indexação: para o Google Imagens, imagem de CSS não existe.
Imagens no sitemap: o campo que quase ninguém preenche
O protocolo de sitemap aceita, por URL, uma lista de imagens associadas — e
o sitemap.ts do App Router expõe isso como o campo images de cada
entrada. É o canal oficial para o Google descobrir imagens que ele não
encontraria pelo HTML, e o que este site usa para dar presença no Google
Imagens aos diagramas dos artigos:
// src/app/sitemap.ts
// Ativos de imagem por artigo, declarados no image sitemap (diagramas
// exportados de /public — os "ativos de link" do cluster).
const postImages: Record<string, string[]> = {
'melhorar-lcp-nextjs': ['/images/blog/melhorar-lcp-nextjs/lcp-subpartes-timeline.webp'],
'lcp-alto-next-js': ['/images/blog/lcp-alto-next-js/preload-scanner-visivel-invisivel.webp'],
// …um ativo por artigo
}
const postPages: MetadataRoute.Sitemap = posts.map(({ frontmatter }) => ({
url: `${base}/blog/${frontmatter.slug}`,
lastModified: toDate(frontmatter.dateModified),
changeFrequency: 'monthly',
priority: 0.7,
images: postImages[frontmatter.slug]?.map((path) => `${base}${path}`),
}))Por que os diagramas precisam desse canal é o assunto da próxima seção.
Diagrama deve ser SVG inline, não imagem
A decisão de arquitetura de conteúdo deste site que nenhum guia de
next/image discute: diagrama que ensina não é <Image> — é <svg>
inline no MDX.
Texto dentro do SVG é rastreável e citável
Todos os rótulos dos diagramas deste site são elementos <text> de verdade
dentro do SVG. O Google lê esse texto como parte da página; um LLM que
processa o HTML lê "preload scanner → não enxerga background-image" direto
do markup. Um PNG com os mesmos rótulos é uma caixa preta — o conteúdo do
diagrama simplesmente não existe como texto. Num site que disputa citação em
respostas geradas por IA, essa diferença é o jogo inteiro.
O trade-off declarado: SVG inline não é tratado como recurso de imagem
pelo Google Imagens. Por isso cada artigo exporta seu diagrama principal
como uma cópia raster em public/images/blog/, cuja única função é
alimentar o campo images do sitemap — o inline serve humano e LLM; a cópia
raster serve o canal de imagem.
O SVG segue o tema; o PNG não
Os diagramas usam cor exclusivamente via token CSS (--foreground,
--color-accent, --color-diagram-*). No tema escuro, o diagrama inteiro
acompanha — texto, eixos, preenchimentos. Um PNG exportado do tema claro
fica com fundo branco estourado no dark mode, ou exige duas exportações e
um <picture> com media query. O SVG inline resolve isso de graça.
Um caso real: duas Image e um download
O componente BlurFillImage deste site renderiza a mesma imagem duas vezes:
uma cópia borrada ao fundo com object-cover (preenche o container) e a
imagem inteira à frente com object-contain. O código real:
// src/components/ui/BlurFillImage.tsx
export function BlurFillImage({
src,
alt,
title,
sizes,
preload,
className,
}: BlurFillImageProps) {
return (
<>
{/* Fundo borrado — preenche o frame */}
<Image
src={src}
alt=""
aria-hidden
fill
sizes={sizes}
preload={preload}
className="scale-150 object-cover blur-2xl"
/>
{/* Imagem real — inteira, sem corte */}
<Image
src={src}
alt={alt}
title={title || alt || undefined}
fill
sizes={sizes}
preload={preload}
className={`object-contain${className ? ` ${className}` : ''}`}
/>
</>
)
}Dois pontos de SEO nesse componente. Primeiro, o custo de rede: as duas
<Image> compartilham o mesmo src e as mesmas dimensões de layout, então
a variante resolvida pelo pipeline é idêntica — o browser baixa uma vez e
reaproveita. Duas tags, um request. Segundo, a acessibilidade que também é
indexação: a cópia de fundo leva alt vazio e aria-hidden, porque ela
é decoração; a descrição mora na imagem da frente. É a regra do alt vazio
aplicada, e não teoria.
Em texto: o mesmo src com a mesma variante resolvida gera um único request
de rede, que o browser reutiliza para as duas renderizações — o efeito
visual de duplicata não duplica o custo.
Como verificar
Fechando o loop, como manda o guia de SEO técnico para Next.js:
- DevTools → Network, filtro Img. Confira o formato servido (AVIF/WebP,
não o original), e no caso de duplicatas como o
BlurFillImage, que o mesmosrcgerou um request só. - View-source. O
altestá no HTML da primeira resposta, descritivo e no idioma da página? As decorativas estão com o atributo vazio? - Sitemap. Abra o
/sitemap.xmlde produção e confirme que as entradas carregam o campo de imagens com URLs absolutas dos arquivos raster. - Lighthouse. As auditorias de imagem (formato, dimensionamento, lazy) passam? E o CLS segue em 0 com as dimensões explícitas?
- JSON-LD dos artigos. Se as páginas declaram
imageno schema deArticle, valide o bloco — o gerador de JSON-LD produz o formato usado neste site.