Desde maio de 2021, o Google usa Core Web Vitals como fator de ranqueamento. Na prática, isso significa que dois sites com conteúdo equivalente — um com LCP de 1,8s e outro com 4,2s — não disputam em igualdade na SERP. O mais rápido tem vantagem estrutural.
Este guia cobre as três métricas atuais, o que as degrada e as técnicas concretas para colocar cada uma no verde — sem enrolação.
As três métricas e seus limites
O Google define três faixas para cada métrica: Bom, Precisa de melhoria e Ruim. A meta é estar no verde (Bom) para pelo menos 75% das pageviews de campo (dados reais de usuários).
- LCP — Largest Contentful Paint: tempo até o maior elemento visível na viewport ser renderizado. Bom: ≤ 2,5s. Ruim: > 4s.
- CLS — Cumulative Layout Shift: soma de todas as mudanças inesperadas de layout durante a vida da página. Bom: ≤ 0,1. Ruim: > 0,25.
- INP — Interaction to Next Paint: latência de resposta às interações do usuário (cliques, teclas, toque). Substituiu o FID em março de 2024. Bom: ≤ 200ms. Ruim: > 500ms.
LCP: o que causa e como resolver
O elemento LCP é normalmente uma imagem hero, uma imagem de destaque ou um bloco de texto grande acima da dobra. O problema quase sempre vem de um desses quatro fatores:
1. Imagem LCP não priorizada
Por padrão, o browser descobre imagens ao fazer parse do HTML e depois as requisita. Para a imagem LCP, isso introduz um delay desnecessário. A solução é sinalizar explicitamente que ela é prioritária:
<!-- Adicionar fetchpriority="high" na imagem LCP -->
<img
src="/hero.webp"
fetchpriority="high"
loading="eager"
width="1200"
height="600"
alt="..."
/>
Para imagens em CSS (background-image), o browser não consegue priorizar sozinho — prefira usar <img> para o elemento LCP sempre que possível, ou use <link rel="preload">:
<link
rel="preload"
as="image"
href="/hero.webp"
fetchpriority="high"
/>
2. Formato de imagem inadequado
JPEG e PNG são pesados para web. WebP reduz o tamanho em 25–35% com qualidade visual idêntica. AVIF vai além — 45–55% menor que JPEG — mas tem suporte mais restrito (89% dos browsers em 2026). Use <picture> para servir o melhor formato disponível:
<picture>
<source srcset="/hero.avif" type="image/avif" />
<source srcset="/hero.webp" type="image/webp" />
<img src="/hero.jpg" fetchpriority="high" alt="..." width="1200" height="600" />
</picture>
3. Fontes bloqueando renderização
Fontes carregadas via Google Fonts adicionam dois round-trips externos antes de qualquer texto ser exibido: DNS lookup + conexão TLS para fonts.googleapis.com, depois para fonts.gstatic.com. A solução mais eficaz é hospedar as fontes você mesmo:
/* Em vez de @import do Google Fonts */
@font-face {
font-family: 'Inter Variable';
font-style: normal;
font-weight: 100 900;
font-display: swap;
src: url('/fonts/inter-variable.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}
O font-display: swap faz o browser exibir texto com a fonte fallback imediatamente, substituindo pela fonte customizada quando ela carregar — elimina o FOIT (Flash of Invisible Text) que contribui para LCP alto.
4. TTFB alto (Time to First Byte)
Se o servidor demora para responder, tudo o que vem depois também atrasa. Para sites estáticos, a solução é CDN na borda (Cloudflare Pages, Vercel Edge). Para aplicações dinâmicas, considere cache HTTP agressivo com stale-while-revalidate e Redis para respostas frequentes.
CLS: o que causa e como resolver
Layout shift ocorre quando elementos se movem após já terem sido pintados. As causas mais comuns:
1. Imagens sem dimensões declaradas
Quando o browser não sabe o tamanho de uma imagem antes de carregá-la, ele não reserva espaço — e o layout "pula" quando ela chega. Sempre declare width e height:
<!-- Ruim: browser não reserva espaço -->
<img src="/banner.webp" alt="..." />
<!-- Bom: aspect ratio calculado antecipadamente -->
<img src="/banner.webp" width="1200" height="400" alt="..." />
Em CSS responsivo, adicione height: auto para que a imagem redimensione mantendo o aspect ratio calculado com os atributos HTML:
img {
max-width: 100%;
height: auto; /* respeita o aspect ratio do width/height do HTML */
}
2. Banners de cookies e notificações que empurram conteúdo
Componentes inseridos dinamicamente no topo da página após o carregamento inicial causam alto CLS. A solução é reservar o espaço com min-height no container antes de o componente ser carregado, ou usar position: fixed para que o banner sobreponha o conteúdo ao invés de deslocá-lo.
3. Fontes causando FOUT (Flash of Unstyled Text)
Quando a fonte customizada carrega e substitui a fallback, se os tamanhos de linha e espaçamento forem diferentes, o layout muda. Use size-adjust, ascent-override e descent-override para alinhar a métrica da fonte fallback com a customizada:
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%;
}
INP: o que causa e como resolver
INP mede a latência de resposta às interações — clique de botão, digitação em campo, toque em item de lista. O principal culpado é o main thread ocupado quando a interação acontece.
1. Tarefas longas no main thread
Qualquer tarefa JavaScript que exceda 50ms é considerada uma "long task" e bloqueia o browser de processar interações. Use o Chrome DevTools Performance tab para identificá-las. A solução é quebrar tarefas longas com scheduler.yield() (ou setTimeout(fn, 0) como fallback):
async function processarListaGrande(items) {
for (let i = 0; i < items.length; i++) {
processar(items[i]);
// Cede o controle ao browser a cada 50 itens
if (i % 50 === 0) {
await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
}
}
}
2. Hidratação pesada de frameworks JavaScript
React, Vue e Angular precisam hidratar componentes no cliente — esse processo bloqueia o main thread. Estratégias para reduzir:
- Islands Architecture (Astro): hidrata apenas componentes interativos, HTML estático permanece estático
- Lazy hydration: use
client:visible(Astro) ounext/dynamiccomssr: falsepara adiar hidratação até o componente entrar na viewport - React Server Components: no Next.js App Router, componentes de servidor não enviam JS para o cliente
3. Event listeners pesados sem debounce
Handlers de scroll, resize e keydown disparados sem throttle ou debounce causam dezenas de execuções por segundo. Use requestAnimationFrame para scroll/resize e debounce para keydown:
// Debounce para inputs
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
input.addEventListener('input', debounce(handleSearch, 300));
Como medir: ferramentas essenciais
Existem dois tipos de dados: dados de campo (usuários reais) e dados de laboratório (simulado). O Google usa dados de campo para ranqueamento — mas dados de laboratório são essenciais durante o desenvolvimento.
- Google Search Console → Experiência da Página: dados de campo agregados por URL. Mostra quais páginas estão em vermelho no CrUX (Chrome User Experience Report).
- PageSpeed Insights: combina dados de campo do CrUX com análise de laboratório via Lighthouse. Use para diagnosticar páginas específicas.
- Chrome DevTools → Performance: gravação frame a frame. Essencial para identificar long tasks e fontes de INP.
- web-vitals (npm): biblioteca oficial do Google para medir Core Web Vitals em campo e enviar para seu próprio analytics.
import { onLCP, onCLS, onINP } from 'web-vitals';
onLCP(metric => console.log('LCP:', metric.value));
onCLS(metric => console.log('CLS:', metric.value));
onINP(metric => console.log('INP:', metric.value));
Checklist de otimização por prioridade
Alto impacto — faça primeiro:
- Converter imagens para WebP/AVIF e declarar width/height em todas as
<img> - Adicionar
fetchpriority="high"na imagem LCP - Hospedar fontes localmente com
font-display: swap - Mover para CDN (Cloudflare Pages, Vercel) para TTFB < 200ms
Médio impacto:
- Lazy load (
loading="lazy") em imagens abaixo da dobra - Quebrar bundles JS com code splitting (dynamic import)
- Remover scripts de terceiros desnecessários (widgets, trackers antigos)
- Reservar espaço para banners e ads com
min-height
Refinamento:
- Implementar
font-metric-overridepara eliminar FOUT - Usar
scheduler.yield()para tarefas longas - Monitorar INP com web-vitals em produção e criar alertas no GA4
Core Web Vitals não é uma tarefa única — é manutenção contínua. Cada novo script de terceiro, cada imagem sem dimensão e cada componente React hidratado sem necessidade é um passo na direção errada. O verde de hoje pode virar amarelo depois de uma atualização de dependência.