
Core Web Vitals: otimizar LCP, CLS e INP
Core Web Vitals afetam o ranking no Google desde maio de 2021. Isso não é teoria — o Google publicou estudos mostrando correlação direta entre páginas com boas métricas de Core Web Vitals e menor taxa de abandono, mas o impacto no ranking é real e mensurável para queries competitivas onde outros fatores estão equilibrados.
O que torna o assunto complexo é que as três métricas (LCP, CLS e INP) medem fenômenos diferentes, têm causas diferentes e exigem soluções diferentes. Um site pode ter LCP excelente e CLS péssimo. Cada métrica precisa ser diagnosticada e resolvida de forma independente.
LCP: por Que é Lento e Como Resolver
LCP (Largest Contentful Paint) mede o tempo até o maior elemento visível na viewport superior ser completamente renderizado. A meta do Google é abaixo de 2,5 segundos; acima de 4 segundos é classificado como "poor".
Em 90% das landing pages, o elemento que determina o LCP é a hero image ou o bloco principal de headline. O diagnóstico começa identificando qual elemento o browser está usando como LCP — o painel de Performance do Chrome DevTools mostra isso na linha "LCP" da timeline.
Causa 1: Imagem não priorizada. O browser descobre imagens no HTML em ordem de aparição e as coloca na fila de download com prioridade padrão. Para a hero image, isso é errado — ela deve ser baixada com prioridade máxima.
<!-- Errado: browser trata como baixa prioridade -->
<img src="/hero.png" alt="Hero" />
<!-- Correto: informa ao browser que é o elemento mais importante -->
<img
src="/hero.png"
alt="Hero"
fetchpriority="high"
loading="eager"
width="1200"
height="630"
/>
No Next.js, o componente next/image aceita a prop priority que faz exatamente isso:
import Image from 'next/image'
<Image
src="/hero.png"
alt="Hero"
width={1200}
height={630}
priority // equivale a fetchpriority="high" + loading="eager"
/>
Causa 2: Formato de imagem errado. PNG e JPEG são significativamente maiores que WebP e AVIF. Uma hero image de 800KB em JPEG pode ser 200KB em WebP e 150KB em AVIF. O next/image converte automaticamente para WebP/AVIF dependendo do suporte do browser.
Causa 3: Render-blocking resources. CSS e JavaScript que bloqueiam a renderização atrasam o LCP. Scripts de chat widget, pixels de ads e heatmaps costumam bloquear o main thread antes da renderização inicial.
<!-- Script de terceiro bloqueando renderização -->
<script src="https://cdn.exemplo.com/widget.js"></script>
<!-- Correto: carregamento deferido -->
<script src="https://cdn.exemplo.com/widget.js" defer></script>
CLS: Identificando e Eliminando Layout Shift
CLS (Cumulative Layout Shift) mede a soma de todos os layout shifts inesperados durante a vida da página. Um layout shift acontece quando um elemento muda de posição depois de já ter sido renderizado. A meta é abaixo de 0,1.
O Chrome DevTools identifica cada layout shift na timeline de Performance, mostrando qual elemento se moveu, quanto se moveu, e o que causou o movimento.
Causa 1: Imagens sem dimensões definidas. Sem width e height, o browser não sabe quanto espaço reservar para a imagem antes de baixá-la. Quando a imagem chega, empurra o conteúdo abaixo dela.
// Errado: sem dimensões
<Image src="/foto.png" alt="Foto" />
// Correto: dimensões definidas garantem reserva de espaço
<Image src="/foto.png" alt="Foto" width={800} height={450} />
Causa 2: Fontes web com FOUT. Quando a fonte web é carregada depois do texto já ter sido renderizado com a fonte de fallback, o texto pode mudar de tamanho ou posição. O font-display: swap troca a fonte de fallback pela web font quando disponível — isso elimina o FOUT mas pode causar CLS se as fontes tiverem métricas muito diferentes.
A solução moderna é ajustar as métricas da fonte de fallback para coincidir com a fonte web:
@font-face {
font-family: 'MinhaFonte';
src: url('/fonts/minha-fonte.woff2') format('woff2');
font-display: swap;
}
/* Fallback com métricas ajustadas para coincidir com MinhaFonte */
@font-face {
font-family: 'MinhaFonte-Fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
O next/font faz esse ajuste automaticamente para Google Fonts.
Causa 3: Conteúdo dinâmico sem espaço reservado. Banners de cookie, notificações e componentes carregados via JavaScript que aparecem depois do layout inicial empurram o conteúdo. A solução é reservar espaço com altura fixa antes do componente carregar:
// Reserva espaço antes do componente dinâmico estar pronto
<div style={{ minHeight: '60px' }}>
{cookieBannerVisible && <CookieBanner />}
</div>
INP: a Nova Métrica que Substituiu FID
INP (Interaction to Next Paint) substituiu o FID (First Input Delay) como métrica oficial em março de 2024. A diferença é fundamental: FID media apenas o delay da primeira interação, INP mede a latência de todas as interações ao longo de toda a sessão.
A meta é abaixo de 200ms. Acima de 500ms é classificado como "poor".
INP alto é quase sempre causado por JavaScript pesado no main thread. Quando o main thread está ocupado (processando analytics, executando componentes React complexos, ou rodando scripts de terceiros), qualquer clique ou toque do usuário fica na fila esperando o thread liberar.
O diagnóstico correto usa o painel de Performance do Chrome DevTools com a gravação de interações ativas. Tarefas longas (Long Tasks > 50ms) aparecem em vermelho na timeline.
// Componente que causa Long Task desnecessária
function ListaGrande({ itens }: { itens: Item[] }) {
return (
<ul>
{itens.map(item => (
<ItemComplexo key={item.id} item={item} />
))}
</ul>
)
}
// Melhorado: renderização virtualizada para listas grandes
import { useVirtualizer } from '@tanstack/react-virtual'
function ListaGrande({ itens }: { itens: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: itens.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60,
})
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div key={virtualItem.key} style={{ transform: `translateY(${virtualItem.start}px)` }}>
<ItemComplexo item={itens[virtualItem.index]} />
</div>
))}
</div>
</div>
)
}
Ferramentas: PageSpeed Insights, CrUX e WebPageTest
| Ferramenta | O que mede | Quando usar |
|---|---|---|
| PageSpeed Insights | Lab data (simulado) + Field data (CrUX) | Diagnóstico rápido e benchmarks |
| Chrome DevTools Performance | Lab data com waterfall detalhado | Debug de causa raiz |
| WebPageTest | Lab data com filmstrip e waterfall | Comparação de variantes e CDN |
| CrUX Dashboard | Field data real por URL e origem | Tendência histórica de usuários reais |
| Search Console > Core Web Vitals | Field data agrupado | Monitoramento de produção e alertas |
O dado mais importante para SEO é o Field Data (CrUX) — são as métricas reais dos seus usuários, não de uma simulação. Uma página pode passar em todos os testes de lab e ainda ter Field Data ruim se seus usuários estão em dispositivos lentos ou redes móveis congestionadas.
Conclusão
Core Web Vitals não são checkbox de compliance — são indicadores de experiência real dos seus usuários. Páginas que carregam rápido, não piscam durante o carregamento e respondem imediatamente a interações convertem mais e ranqueiam melhor.
O Next.js resolve estruturalmente boa parte dos problemas de Core Web Vitals: exportação estática elimina TTFB alto, next/image resolve LCP e CLS de imagens, e next/font elimina FOUT sem CLS. No SystemForge, entregamos projetos com relatório de Core Web Vitals como parte do processo — nenhuma landing page sai com LCP acima de 2,5s ou CLS acima de 0,1.
Precisa de uma Landing Page?
Criamos landing pages de alta conversão com SEO e performance.
Saiba mais →Precisa de ajuda?

