Performance SEO Web Frontend

Core Web Vitals: guia prático de otimização para desenvolvedores

Entenda LCP, CLS e INP na prática: como medir, o que causa degradação e as técnicas mais eficazes para colocar seu site no verde.

N
Neryx Digital Architects
11 de janeiro de 2026
14 min de leitura
280 profissionais leram
Categoria: Geral Público: Times de engenharia e produto Etapa: Aprendizado

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) ou next/dynamic com ssr: false para 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-override para 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.

Quer transformar esse aprendizado em plano de ação?

Se o tema deste artigo se parece com o momento do seu time, podemos ajudar a decidir o próximo passo com clareza.

Falar com um especialista

Newsletter

Receba artigos como este no seu e-mail

Conteúdo técnico sobre arquitetura de software, .NET, IA e gestão de produto. Sem spam.