Alternar tema

Otimização de desempenho frontend na prática: guia para nota máxima no Core Web Vitals

Easton editorial illustration: component assembly loom

O Lighthouse está marcando apenas 60 pontos, e você precisa chegar a 90 em uma semana. Ao abrir o Chrome DevTools, ainda é preciso entender o que significam LCP, FID e CLS. Para quem trabalha com desenvolvimento frontend, otimizar o desempenho muitas vezes aparece como uma tarefa extra e inesperada.

Já caí em várias armadilhas: na primeira otimização, configurei carregamento preguiçoso para todas as imagens e a nota de LCP piorou — a imagem principal acima da dobra simplesmente não deveria ser carregada dessa forma. Em outra ocasião, passei dois dias estudando estratégias de cache com Service Worker e ganhei apenas 2 pontos. Este artigo não é uma introdução teórica: é um guia prático para ver resultado em duas semanas, entender quais otimizações oferecem o maior ROI, quais erros evitar e como elevar a nota do Lighthouse de 60 para mais de 90 seguindo prioridades claras.

70%+
dos problemas de LCP
vêm de imagens
41%
ganho de compressão
AVIF vs JPEG
2 semanas
ciclo de otimização
60 pontos → 90+
Source: Dados práticos

Capítulo 1: entendendo as três métricas do Core Web Vitals

Não se assuste com as siglas em inglês. Em resumo, elas respondem a três perguntas: a página carrega rápido, responde ao clique e permanece visualmente estável?

O que é Core Web Vitals

Em 2020, o Google apresentou três métricas de experiência do usuário. Elas não afetam apenas a experiência: também influenciam diretamente o SEO. Mesmo que o conteúdo seja excelente, um site com baixo desempenho pode perder posições. Em março de 2024, houve ainda uma mudança importante: o INP substituiu o FID como métrica principal. Se você ainda estiver otimizando apenas o FID, já ficou para trás.

LCP — Largest Contentful Paint

O LCP mede a velocidade de carregamento do principal conteúdo da página, geralmente uma imagem grande, um título ou um vídeo acima da dobra. Os valores de referência são:

  • Menos de 2,5 segundos: bom (verde)
  • De 2,5 a 4 segundos: precisa melhorar (amarelo)
  • Acima de 4 segundos: ruim (vermelho)

Gosto de comparar o LCP ao tempo que um restaurante leva para servir o prato principal. Se você esperar 10 minutos e ele ainda não chegar, certamente ficará irritado, não é?

Um dado real: quando o carregamento de uma página aumenta de 3 para 5 segundos, a taxa de rejeição cresce 38%. No celular, se o carregamento passar de 3 segundos, 53% dos usuários saem diretamente. Ao otimizar bem o LCP, a taxa de conversão pode subir de 7% a 15%.

38%
aumento na taxa de rejeição
Source: Tempo de carregamento de 3 s → 5 s

INP — Interaction to Next Paint

Essa métrica passou a valer em março de 2024, substituindo oficialmente o FID. O INP mede a rapidez com que a página responde às interações do usuário, incluindo o ciclo completo de resposta a cliques, digitação e toques. Os valores são:

  • Menos de 200 ms: bom (verde)
  • De 200 a 500 ms: precisa melhorar (amarelo)
  • Acima de 500 ms: ruim (vermelho)

Por que o Google substituiu o FID? Porque o FID media apenas o “atraso da primeira entrada”, enquanto o INP cobre todo o processo de interação. É como fazer um pedido: o FID verifica somente se o garçom ouviu você, enquanto o INP acompanha tudo, do pedido até o prato chegar à mesa.

Na primeira vez que medi um projeto, o INP estava em 650 ms. Era preciso esperar mais de meio segundo após clicar em um botão, então não surpreendia que os usuários reclamassem de travamentos. Depois descobri que o problema vinha de um embed automático do YouTube. Ao removê-lo, o INP caiu para 220 ms, e a navegação ficou perceptivelmente mais fluida.

CLS — Cumulative Layout Shift

O CLS mede a estabilidade visual da página. Já aconteceu de você estar prestes a clicar em um botão, a página se mover de repente e o clique cair em um anúncio? Esse é um problema de CLS. Os valores são:

  • Menos de 0,1: bom (verde)
  • De 0,1 a 0,25: precisa melhorar (amarelo)
  • Acima de 0,25: ruim (vermelho)

Quando vi um CLS de 0,5 pela primeira vez, achei que fosse um valor pequeno. Depois descobri que ele já indicava um problema grave. As causas mais comuns incluem imagens sem largura e altura definidas, anúncios inseridos repentinamente e flashes durante o carregamento de fontes (FOIT).


Capítulo 2: otimização de LCP — o maior ROI em desempenho

Por que coloco o LCP em primeiro lugar?

  • Impacto mais direto: o usuário percebe imediatamente
  • Maior margem para melhorar: é comum reduzir o LCP de 5 para menos de 2 segundos
  • Soluções maduras: otimização de imagens e aceleração por CDN são técnicas consolidadas
  • Maior ROI: pouco investimento, resultado rápido e excelente custo-benefício

Fluxo completo de otimização do Core Web Vitals

Otimização completa de LCP, INP e CLS para elevar a nota do Lighthouse de 60 para mais de 90 em duas semanas

Estimated time: PT2W

  1. 1

    Step 1: Prioridade P0: otimização de imagens (LCP)

    As imagens respondem por mais de 70% dos problemas de LCP e oferecem o maior ROI:
  2. 2

    Step 2: Prioridade P0: adiar scripts de terceiros (INP)

    Scripts de terceiros são o principal gargalo do INP:
  3. 3

    Step 3: Prioridade P0: definir dimensões das imagens (CLS)

    O CLS é a métrica mais fácil de corrigir:
  4. 4

    Step 4: Prioridade P1: divisão de código e otimização de recursos

    Divisão de código e recursos:
  5. 5

    Step 5: Prioridade P1: fontes e servidor

    Otimização de fontes e servidor:
  6. 6

    Step 6: Prioridade P2: otimizações avançadas

    Otimizações avançadas, com ROI médio e alta dificuldade:

1. Otimização de imagens (ROI: ⭐⭐⭐⭐⭐)

Este é o ponto mais importante e responde por mais de 70% dos problemas de LCP. Sempre começo pelas imagens quando otimizo o desempenho.

a) Use formatos modernos de imagem

Não subestime o formato: trocar JPEG por AVIF pode economizar 300 KB em uma única imagem.

<!-- Solução 1: use a tag <picture> e deixe o navegador escolher o melhor formato -->
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Imagem hero"> <!-- fallback para navegadores antigos -->
</picture>
// Solução 2: otimização automática do Next.js (recomendado)
import Image from 'next/image'
<Image
  src="/hero.jpg"
  width={1200}
  height={600}
  priority // Importante: a imagem de LCP precisa de priority, sem carregamento preguiçoso!
/>

Veja os números reais:

  • AVIF comprime 41% melhor que JPEG
  • WebP comprime 30% melhor que JPEG
  • Caso prático: em uma página inicial de e-commerce, reduzi a imagem hero de 500 KB para 120 KB em AVIF. O LCP caiu de 4,2 para 2,1 segundos, praticamente pela metade.

b) Carregamento preguiçoso de imagens, exceto a de LCP

Há uma armadilha em que já caí: apliquei carregamento preguiçoso também à imagem de LCP, e a nota piorou.

<!-- ❌ Incorreto: não use carregamento preguiçoso na imagem de LCP -->
<img src="hero.jpg" loading="lazy">
<!-- ✅ Correto: use eager na imagem principal acima da dobra e lazy nas demais -->
<img src="hero.jpg" loading="eager"> <!-- imagem de LCP -->
<img src="product1.jpg" loading="lazy"> <!-- apenas imagens mais abaixo -->
<img src="product2.jpg" loading="lazy">

Lembre-se: nenhuma imagem visível na primeira tela deve usar carregamento preguiçoso. Reserve-o para imagens fora da primeira tela.

c) Imagens responsivas

Quem acessa pelo celular não precisa baixar a imagem grande do desktop. O srcset pode economizar 60% de tráfego.

<img
  src="hero-800w.jpg"
  srcset="hero-400w.jpg 400w,
          hero-800w.jpg 800w,
          hero-1200w.jpg 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1000px) 800px,
         1200px"
  alt="Imagem hero"
/>

Resultado real: no celular, carregar a imagem de 400w em vez da versão de 1200w pode melhorar o LCP em 1 segundo.

d) CDN e preload de imagens

CDN não é luxo, é necessidade. O Alibaba Cloud OSS custa apenas algumas dezenas de yuans por ano.

<!-- Faça preload da imagem essencial para o navegador priorizá-la -->
<link rel="preload" as="image" href="hero.jpg">
<!-- Use uma CDN de imagens, com conversão automática de formato e compressão -->
<!-- Tencent Cloud COS, Alibaba Cloud OSS e Cloudflare Images oferecem esse recurso -->
<img src="https://cdn.example.com/hero.jpg?x-oss-process=image/format,webp/quality,80">

e) Dimensões e compressão de imagens

Ferramentas recomendadas:

  • TinyPNG: compressão online, simples e direta
  • ImageOptim: excelente ferramenta para Mac e compressão em lote
  • Squoosh: ferramenta do Google com suporte a AVIF

Regras de compressão:

  • Imagem principal acima da dobra com qualidade de 80% — a diferença visual é mínima
  • Outras imagens com 70% — já é suficiente
  • Largura igual ao dobro da especificação do design, para telas de alta resolução

f) Evite imagens grandes em Base64 inline

// ❌ Não incorpore imagens grandes, acima de 10 KB
// Isso aumenta o tamanho do HTML e atrasa a renderização
const heroImage = 'data:image/jpeg;base64,/9j/4AAQSkZJRg...' // 500KB
// ✅ Use Base64 apenas em ícones pequenos, abaixo de 10 KB
// Por exemplo, ícones de carregamento ou SVGs simples
const icon = 'data:image/svg+xml;base64,PHN2ZyB3aWR...' // 2KB

2. Otimização do tempo de resposta do servidor (ROI: ⭐⭐⭐⭐)

a) Use CDN

Coloque todos os recursos estáticos em uma CDN. O HTML também pode ser armazenado em cache na CDN, em conjunto com SSG/ISR. Já vi casos em que o TTFB caiu de 200 ms para 50 ms, com uma diferença perceptível para o usuário.

b) Renderização no servidor (SSR) ou geração estática (SSG)

// Next.js - geração estática (primeira opção, oferece o melhor desempenho)
export async function getStaticProps() {
  const data = await fetchData()
  return {
    props: { data },
    revalidate: 60 // ISR: gera novamente após 60 segundos
  }
}
// Ou renderização no servidor, quando os dados precisam estar atualizados em tempo real
export async function getServerSideProps() {
  const data = await fetchData()
  return { props: { data } }
}

c) Otimize as consultas ao banco de dados

  • Adicione índices e não se esqueça de analisar com explain
  • Use Redis para armazenar dados acessados com frequência
  • Evite consultas N+1 com join ou dataloader

3. Otimização do carregamento de recursos (ROI: ⭐⭐⭐)

a) CSS crítico inline

<!-- Incorpore o CSS crítico da primeira tela no <head> para não bloquear a renderização -->
<style>
  .hero {
    width: 100%;
    height: 600px;
    background: #f0f0f0;
  }
  .nav {
    position: fixed;
    top: 0;
    width: 100%;
  }
</style>
<!-- Adie o CSS não essencial -->
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

b) Otimização de fontes

/* Use font-display para evitar FOIT (Flash of Invisible Text) */
@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Exibe imediatamente a fonte de fallback e evita uma tela em branco */
}
<!-- Faça preload da fonte essencial -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

Capítulo 3: otimização de INP — interações mais fluidas

Scripts de terceiros são o maior inimigo do INP. O caso mais extremo que já vi foi uma página com 12 scripts de terceiros: Google Analytics, Facebook Pixel, widget de atendimento, scripts de anúncios… O INP passou diretamente de 800 ms.

1. Otimização da execução de JavaScript (ROI: ⭐⭐⭐⭐)

a) Divisão de código (Code Splitting)

Divisão de código pode parecer algo sofisticado, mas significa apenas “carregar sob demanda”: o código de cada página só é baixado quando o usuário a acessa.

// React - divisão de código no nível das rotas
import { lazy, Suspense } from 'react'
const Dashboard = lazy(() => import('./Dashboard'))
const Profile = lazy(() => import('./Profile'))
function App() {
  return (
    <Suspense fallback={<div>Carregando...</div>}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Routes>
    </Suspense>
  )
}
// Vue 3 - também oferece suporte a componentes assíncronos
const Dashboard = defineAsyncComponent(() => import('./Dashboard.vue'))

Resultado real: em um painel administrativo, o bundle inicial caiu de 1,2 MB para 200 KB, e o INP passou de 450 ms para 180 ms. A página abriu mais de duas vezes mais rápido para o usuário.

b) Divida tarefas longas

Quando a thread principal fica bloqueada por mais de 50 ms, temos uma tarefa longa, o que faz o INP disparar.

// ❌ Tarefa longa que bloqueia a thread principal e congela a interface
function processLargeData(data) {
  for (let i = 0; i < 10000; i++) {
    // Cálculo complexo de 10 mil itens de uma só vez
    heavyCalculation(data[i])
  }
}
// ✅ Use requestIdleCallback para dividir o trabalho em tarefas menores
function processLargeData(data) {
  let index = 0
  function processChunk() {
    const chunkSize = 100 // Processa 100 itens de cada vez
    let count = 0
    while (index < data.length && count < chunkSize) {
      heavyCalculation(data[index])
      index++
      count++
    }
    if (index < data.length) {
      // Se ainda houver itens, continue no próximo período ocioso
      requestIdleCallback(processChunk)
    }
  }
  requestIdleCallback(processChunk)
}

c) Use Web Workers

Mova cálculos complexos para um Worker para não bloquear a thread principal.

// worker.js
self.onmessage = (e) => {
  const result = complexCalculation(e.data) // O cálculo complexo é executado no Worker
  self.postMessage(result)
}
// main.js
const worker = new Worker('worker.js')
worker.postMessage(data)
worker.onmessage = (e) => {
  console.log('Result:', e.data)
  // Receba o resultado e atualize a interface
}

2. Otimização de scripts de terceiros (ROI: ⭐⭐⭐⭐⭐)

Scripts de terceiros são como convidados para o jantar: quanto mais gente chega, maior a confusão. Todos eles disputam os mesmos recursos.

a) Adie scripts não essenciais

<!-- ❌ Carregamento bloqueante, que interrompe a renderização da página -->
<script src="analytics.js"></script>
<!-- ✅ Adie até o carregamento da página terminar -->
<script defer src="analytics.js"></script>
<!-- ✅ Ou aplique manualmente um atraso de 3 segundos, de forma mais agressiva -->
<script>
  window.addEventListener('load', () => {
    setTimeout(() => {
      const script = document.createElement('script')
      script.src = 'analytics.js'
      document.body.appendChild(script)
    }, 3000) // Carrega o script de análise depois de o usuário navegar por 3 segundos
  })
</script>

b) Use o padrão Facade para adiar componentes pesados

Um embed de vídeo do YouTube tem mais de 1 MB. Carregá-lo imediatamente prejudica muito o INP.

// Thumbnail leve do YouTube; o vídeo real só é carregado após o clique
function VideoFacade({ videoId }) {
  const [showVideo, setShowVideo] = useState(false)
  if (!showVideo) {
    return (
      <div
        className="video-facade"
        style={{
          backgroundImage: `url(https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg)`,
          cursor: 'pointer'
        }}
        onClick={() => setShowVideo(true)}
      >
        <button className="play-button">▶ Reproduzir vídeo</button>
      </div>
    )
  }
  return <iframe src={`https://www.youtube.com/embed/${videoId}`} />
}

Resultado real: ao remover o embed automático do YouTube de uma página de blog, o INP caiu de 650 ms para 220 ms.

3. Otimização de eventos (ROI: ⭐⭐⭐)

a) Debounce e throttle

import { debounce, throttle } from 'lodash'
// Debounce no campo de busca: só dispara 300 ms após o usuário parar de digitar
const handleSearch = debounce((value) => {
  fetchSearchResults(value)
}, 300)
// Throttle no evento de rolagem: dispara no máximo uma vez a cada 100 ms
const handleScroll = throttle(() => {
  updateScrollPosition()
}, 100)

b) Use listeners de eventos passivos

// Melhora o desempenho da rolagem ao informar que preventDefault não será chamado
window.addEventListener('scroll', handleScroll, { passive: true })
window.addEventListener('touchmove', handleTouch, { passive: true })

Capítulo 4: otimização de CLS — evite saltos na tela

O CLS é como alguém empurrar o livro para cima enquanto você está lendo: você precisa procurar novamente a linha em que estava, o que é extremamente irritante. A boa notícia é que o CLS é a métrica mais fácil de corrigir.

1. Defina as dimensões de imagens e vídeos (ROI: ⭐⭐⭐⭐⭐)

Imagens sem largura e altura são a principal causa de CLS, mas também o problema mais simples de corrigir.

<!-- ❌ Sem largura e altura, a página se move durante o carregamento -->
<img src="photo.jpg" alt="Foto">
<!-- ✅ Com largura e altura, o navegador reserva o espaço antecipadamente -->
<img src="photo.jpg" width="800" height="600" alt="Foto">
<!-- ✅ Ou use CSS aspect-ratio, disponível em navegadores modernos -->
<style>
  img {
    width: 100%;
    aspect-ratio: 16 / 9;
  }
</style>

Caso real: um site de notícias definiu largura e altura em todas as imagens, e o CLS caiu de 0,35 para 0,05. Simples assim.

2. Otimize o carregamento de fontes (ROI: ⭐⭐⭐⭐)

a) Use font-display: swap

@font-face {
  font-family: 'CustomFont';
  src: url('font.woff2') format('woff2');
  font-display: swap; /* Evita FOIT, a tela em branco durante o carregamento da fonte */
}

b) Faça preload das fontes essenciais

<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

c) Use fontes do sistema ou Variable Fonts

/* Fontes do sistema: sem download e sem atraso */
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
/* Variable Font: um único arquivo inclui todos os pesos, de 100 a 900 */
@font-face {
  font-family: 'Inter';
  src: url('Inter-Variable.woff2') format('woff2-variations');
  font-weight: 100 900;
}

3. Reserve espaço para conteúdo dinâmico (ROI: ⭐⭐⭐⭐)

a) Reserve espaço para anúncios

.ad-container {
  min-height: 250px; /* Reserva a altura do anúncio e evita movimentos antes do carregamento */
  background: #f0f0f0; /* Fundo do placeholder */
}

b) Skeleton Screen

O skeleton screen não serve apenas para deixar a interface bonita. O mais importante é que ele mantém o layout estável.

// Exiba o skeleton antes do carregamento para evitar mudanças quando o conteúdo aparecer
function ProductCard({ loading, data }) {
  if (loading) {
    return (
      <div className="skeleton">
        <div className="skeleton-image" style={{ width: '100%', height: '200px', background: '#e0e0e0' }} />
        <div className="skeleton-title" style={{ width: '80%', height: '20px', background: '#e0e0e0', margin: '10px 0' }} />
        <div className="skeleton-price" style={{ width: '40%', height: '20px', background: '#e0e0e0' }} />
      </div>
    )
  }
  return (
    <div className="product">
      <img src={data.image} alt={data.title} />
      <h3>{data.title}</h3>
      <p>{data.price}</p>
    </div>
  )
}

4. Evite inserir conteúdo acima do conteúdo existente (ROI: ⭐⭐⭐⭐⭐)

// ❌ Inserir um banner no topo desloca todo o conteúdo e faz o CLS disparar
<div>
  {showBanner && <Banner />}
  <Content />
</div>
// ✅ Use posicionamento fixed para não afetar o layout
<div>
  {showBanner && <Banner style={{ position: 'fixed', top: 0, zIndex: 1000 }} />}
  <Content style={{ marginTop: showBanner ? '60px' : '0' }} />
</div>

5. Use transform em vez de top/left nas animações (ROI: ⭐⭐⭐)

Já vi gente usar top em animações apenas para criar um efeito chamativo, e o CLS disparou.

/* ❌ Aciona layout e causa CLS */
.element {
  position: relative;
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { top: -100px; } /* Alterar top aciona reflow */
  to { top: 0; }
}
/* ✅ Use transform, que aciona apenas composite e aproveita a GPU */
.element {
  animation: slideIn 0.3s;
}
@keyframes slideIn {
  from { transform: translateY(-100px); }
  to { transform: translateY(0); }
}

Capítulo 5: caso prático completo — fluxo de otimização

Definir prioridades é essencial. Não comece pelo SSR; otimize primeiro as imagens. Na minha primeira otimização, só definir largura e altura das imagens elevou a nota em 10 pontos. Foram pontos praticamente de graça.

Matriz de prioridades de otimização

OtimizaçãoROIDificuldadePrioridade
Definir largura e altura das imagensMuito altoMuito baixaP0
Converter o formato das imagensMuito altoBaixaP0
Otimizar a imagem de LCPMuito altoBaixaP0
Adiar scripts de terceirosMuito altoBaixaP0
Otimizar fontesAltoBaixaP1
Dividir o códigoAltoMédiaP1
CDNAltoBaixaP1
SSR/SSGMédioAltaP2
Web WorkersBaixoAltaP3

Ferramentas e comandos de medição

# 1. Lighthouse (a principal referência)
# Chrome DevTools > Lighthouse > Generate report
# Use o modo anônimo para evitar que extensões do Chrome interfiram na nota
# 2. WebPageTest (teste em dispositivos reais)
# https://webpagetest.org
# Permite escolher diferentes regiões e velocidades de rede
# 3. Painel Performance do Chrome DevTools
# Grave o carregamento e analise os gargalos
# É possível ver a duração de cada tarefa
# 4. Análise de pacotes npm
npx webpack-bundle-analyzer
# Mostra visualmente qual pacote é maior
# 5. Análise de imagens
npx sharp-cli info image.jpg
# Verifica dimensões, formato e tamanho da imagem

Armadilhas a evitar

  1. ❌ Não teste o Lighthouse em produção; use o modo anônimo ou desative as extensões
  2. ❌ Não faça apenas um teste; execute pelo menos três e calcule a média, pois a rede oscila bastante
  3. ❌ Não teste apenas no desktop; o celular é mais importante e representa 75% do tráfego
  4. ❌ Não ignore o Network throttling; simule uma rede lenta, como Fast 3G
  5. ❌ Nunca aplique carregamento preguiçoso à imagem de LCP — eu já caí nessa armadilha

Conclusão

Otimização de desempenho não é uma tarefa feita uma única vez, mas um processo contínuo. É como se exercitar: exige consistência e disciplina. Ainda assim, ver a nota do Lighthouse subir de 60 para 90 traz uma sensação real de conquista.

Otimizar o desempenho não é apenas trabalho técnico; é também uma forma de respeitar a experiência do usuário. Cada segundo economizado ajuda a manter mais pessoas no site. O usuário de celular tem apenas 3 segundos de paciência, e agora você sabe como reduzir esse tempo para 1 segundo.

Seguimos juntos para que cada usuário tenha uma navegação fluida.

FAQ

Quais são os valores de referência das três métricas do Core Web Vitals?
LCP (Largest Contentful Paint):
• menos de 2,5 s é bom, de 2,5 a 4 s precisa melhorar e acima de 4 s é ruim

INP (Interaction to Next Paint):
• menos de 200 ms é bom, de 200 a 500 ms precisa melhorar e acima de 500 ms é ruim
• em março de 2024, o INP substituiu o FID como métrica principal

CLS (Cumulative Layout Shift):
• menos de 0,1 é bom, de 0,1 a 0,25 precisa melhorar e acima de 0,25 é ruim

Dados importantes:
• quando o carregamento aumenta de 3 para 5 segundos, a taxa de rejeição cresce 38%
• no celular, 53% dos usuários saem quando o carregamento passa de 3 segundos
• otimizar o LCP pode elevar a taxa de conversão em 7% a 15%
Como otimizar o LCP (Largest Contentful Paint)?
As imagens respondem por mais de 70% dos problemas de LCP:

1) Use formatos modernos (AVIF comprime 41% melhor e WebP, 30%)
• em um caso prático, a imagem hero caiu de 500 KB para 120 KB e o LCP, de 4,2 s para 2,1 s

2) A imagem de LCP deve usar priority e nunca carregamento preguiçoso; para a imagem principal acima da dobra, use eager

3) Imagens responsivas com srcset economizam 60% de tráfego

4) Use CDN e preload para imagens essenciais

5) Otimize o tempo de resposta do servidor (CDN, SSR/SSG e consultas ao banco de dados)

Prioridade: otimizar imagens oferece o maior ROI, exige pouco esforço e produz resultado rápido.
Como otimizar o INP (Interaction to Next Paint)?
Otimizações principais:

1) Divisão de código no nível das rotas, com React lazy ou Vue defineAsyncComponent
• em um caso prático, o bundle inicial caiu de 1,2 MB para 200 KB e o INP, de 450 ms para 180 ms

2) Adie scripts de terceiros com defer ou atraso manual de 3 segundos e use o padrão Facade para componentes pesados

3) Remova conteúdo de terceiros incorporado automaticamente, como embeds automáticos do YouTube; o INP caiu de 650 ms para 220 ms

4) Divida tarefas longas com requestIdleCallback

5) Use Web Workers para cálculos complexos

6) Aplique debounce, throttle e listeners de eventos passivos
Como otimizar o CLS (Cumulative Layout Shift)?
O CLS é a métrica mais fácil de corrigir:

1) Defina largura e altura das imagens com os atributos width e height ou com CSS aspect-ratio
• em um caso prático, o CLS caiu de 0,35 para 0,05

2) Otimize fontes com font-display: swap para evitar FOIT, preload das fontes essenciais e fontes do sistema ou Variable Fonts

3) Reserve espaço para conteúdo dinâmico com min-height em anúncios e skeleton screens de layout estável

4) Evite inserir conteúdo acima do conteúdo existente; use posicionamento fixed

5) Em animações, use transform em vez de top/left, pois ele aciona apenas composite, não layout
Qual é a ordem de prioridade na otimização de desempenho?
Prioridade P0 (ROI muito alto e baixa dificuldade):
• definir largura e altura das imagens
• converter imagens para AVIF/WebP
• otimizar a imagem de LCP, com priority e sem carregamento preguiçoso
• adiar scripts de terceiros

Prioridade P1 (ROI alto):
• otimizar fontes com font-display: swap
• dividir o código
• usar CDN

Prioridade P2 (ROI médio e alta dificuldade):
• SSR/SSG

Prioridade P3 (ROI baixo e alta dificuldade):
• Web Workers

Recomendação: comece pelo P0. Só definir as dimensões das imagens pode render 10 pontos quase de graça. Não comece imediatamente pelo SSR; primeiro, otimize bem as imagens.
A imagem de LCP pode usar carregamento preguiçoso?
De jeito nenhum. Essa é uma das armadilhas mais comuns.

A imagem de LCP, normalmente a imagem principal acima da dobra, deve usar priority ou loading="eager". Não aplique carregamento preguiçoso. Ele deve ser reservado para imagens fora da primeira tela.

Forma incorreta:
<img src="hero.jpg" loading="lazy"> faz a nota de LCP piorar

Forma correta:
• use eager na imagem principal acima da dobra e carregamento preguiçoso apenas nas demais
• lembre-se: nenhuma imagem visível na primeira tela deve usar carregamento preguiçoso
Como medir e testar o Core Web Vitals?
Ferramentas de medição:

1) Lighthouse, no Chrome DevTools; é a principal referência, e vale usar o modo anônimo para evitar interferência de extensões

2) WebPageTest, para testar em dispositivos reais e selecionar diferentes regiões e velocidades de rede

3) Painel Performance do Chrome DevTools, para gravar o carregamento e analisar gargalos

4) webpack-bundle-analyzer, para visualizar quais pacotes são maiores

5) sharp-cli info, para verificar dimensões, formato e tamanho das imagens

Armadilhas a evitar:
• não teste em produção
• não faça um único teste; execute pelo menos três e calcule a média
• não teste apenas no desktop; o celular é mais importante e representa 75% do tráfego
• não ignore o Network throttling, que simula redes lentas

16 min de leitura · Publicado em: 24 nov 2025 · Atualizado em: 8 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog