Core Web Vitals: guia técnico para melhorar LCP, CLS e INP
1. Fundamentos dos Core Web Vitals e seu Impacto no SEO e UX
Os Core Web Vitals são um conjunto de métricas definidas pelo Google que medem aspectos fundamentais da experiência do usuário na web. As três métricas principais são:
- Largest Contentful Paint (LCP): mede o tempo de carregamento do maior elemento visível na janela de visualização. Ideal: ≤ 2,5 segundos.
- Cumulative Layout Shift (CLS): quantifica mudanças inesperadas de layout. Ideal: ≤ 0,1.
- Interaction to Next Paint (INP): avalia a capacidade de resposta a interações do usuário. Ideal: ≤ 200 milissegundos.
Essas métricas impactam diretamente o ranqueamento em buscadores, a taxa de rejeição e a satisfação do usuário. Ferramentas como Lighthouse, PageSpeed Insights, Chrome UX Report e a Web Vitals API são essenciais para medição e diagnóstico.
2. Largest Contentful Paint (LCP): Otimização do Maior Elemento Visível
Identificação do elemento LCP
Para detectar programaticamente o elemento LCP, utilize a Performance Observer API:
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('Elemento LCP:', lastEntry.element);
console.log('Tempo LCP:', lastEntry.startTime);
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
Técnicas de carregamento prioritário
Atribua fetchpriority="high" ao elemento LCP identificado:
<img src="hero.webp" fetchpriority="high" alt="Imagem principal" />
Pré-carregue fontes críticas no <head>:
<link rel="preload" href="/fonts/inter-var.woff2" as="font" type="font/woff2" crossorigin>
Redução de tempo de resposta do servidor
O TTFB (Time to First Byte) deve ser inferior a 800ms. Implemente:
- CDN com edge caching
- Compressão Brotli ou Gzip
- Cache de banco de dados e Redis
- Otimização de consultas SQL
3. Cumulative Layout Shift (CLS): Eliminando Mudanças Repentinas de Layout
Causas comuns de CLS
- Imagens sem dimensões explícitas
- Anúncios e embeds que inserem conteúdo após o carregamento
- Fontes web que causam reflow (FOUT/FOIT)
- Iframes sem altura definida
Boas práticas de dimensionamento
Sempre declare width e height em imagens e use aspect-ratio no CSS:
<img src="banner.jpg" width="1200" height="630" alt="Banner" style="aspect-ratio: 1200/630" />
Para elementos responsivos, use placeholders proporcionais:
<div style="padding-bottom: 56.25%; position: relative;">
<img src="video-thumbnail.jpg" style="position: absolute; width: 100%; height: 100%;" />
</div>
Estratégias para conteúdo dinâmico
Reserve espaço para anúncios com dimensões fixas:
<div class="ad-container" style="min-height: 250px; width: 300px;">
<!-- Anúncio será inserido aqui -->
</div>
Animações devem usar transform em vez de propriedades que afetam layout:
/* Ruim: causa CLS */
.element { animation: slide 1s; }
@keyframes slide { from { margin-left: -100px; } to { margin-left: 0; } }
/* Bom: não causa CLS */
.element { animation: slide 1s; }
@keyframes slide { from { transform: translateX(-100px); } to { transform: translateX(0); } }
4. Interaction to Next Paint (INP): Melhorando a Capacidade de Resposta a Interações
Entendendo o INP
O INP mede o tempo desde a interação do usuário (clique, toque, tecla) até o próximo frame pintado. Use a Web Vitals API para monitorar:
import { onINP } from 'web-vitals';
onINP((metric) => {
console.log('INP:', metric.value, 'ms');
// Enviar para analytics
});
Otimização do thread principal
Divida tarefas longas (>50ms) usando setTimeout ou requestIdleCallback:
function processLargeArray(items) {
const chunkSize = 100;
let index = 0;
function processChunk() {
const end = Math.min(index + chunkSize, items.length);
for (let i = index; i < end; i++) {
// Processar item
}
index = end;
if (index < items.length) {
setTimeout(processChunk, 0);
}
}
processChunk();
}
Utilize Web Workers para processamento pesado:
// main.js
const worker = new Worker('worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (event) => {
// Processar resultado
};
// worker.js
self.onmessage = (event) => {
const result = heavyComputation(event.data);
self.postMessage(result);
};
Redução de bloqueio de renderização
Implemente debouncing em eventos de alta frequência:
function debounce(fn, delay = 100) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
window.addEventListener('scroll', debounce(() => {
// Lógica de scroll
}, 50));
Use virtual scrolling para listas grandes:
// Implementação simplificada de virtual scrolling
const container = document.querySelector('.scroll-container');
const itemHeight = 50;
const totalItems = 10000;
container.addEventListener('scroll', () => {
const startIndex = Math.floor(container.scrollTop / itemHeight);
const endIndex = startIndex + Math.ceil(container.clientHeight / itemHeight) + 1;
renderVisibleItems(startIndex, endIndex);
});
5. Estratégias Avançadas de Performance para LCP
Otimização de imagens
Converta para formatos modernos e use CDN com transformação dinâmica:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" fetchpriority="high" width="800" height="450" alt="Otimizada">
</picture>
Carregamento crítico de CSS
Faça inlining do CSS acima da dobra e adie o restante:
<style>
/* CSS crítico para o conteúdo acima da dobra */
.hero { display: flex; align-items: center; ... }
</style>
<link rel="preload" href="/styles/full.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
Server-Side Rendering (SSR)
Entregue HTML pré-renderizado para reduzir o tempo de LCP:
// Exemplo com Next.js
export async function getServerSideProps() {
const data = await fetchAPI();
return { props: { data } };
}
6. Monitoramento Contínuo e Automação de Métricas
Configuração de Web Vitals API
Colete dados reais de usuários (RUM) no front-end:
import { onLCP, onCLS, onINP } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id
});
navigator.sendBeacon('/analytics', body);
}
onLCP(sendToAnalytics);
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
Integração com ferramentas de observabilidade
Configure alertas de regressão em plataformas como Datadog ou Sentry:
// Exemplo com Sentry
import * as Sentry from '@sentry/react';
onLCP((metric) => {
if (metric.rating === 'poor') {
Sentry.captureMessage('LCP degradado', { extra: metric });
}
});
Pipeline de CI/CD com Lighthouse CI
Garanta que mudanças não degradem as métricas antes do deploy:
# lighthouse-ci.config.js
module.exports = {
ci: {
collect: {
url: ['https://example.com'],
numberOfRuns: 3
},
assert: {
assertions: {
'largest-contentful-paint': ['warn', { maxNumericValue: 2500 }],
'cumulative-layout-shift': ['warn', { maxNumericValue: 0.1 }],
'interaction-to-next-paint': ['warn', { maxNumericValue: 200 }]
}
},
upload: {
target: 'temporary-public-storage'
}
}
};
Execute no CI:
npx lhci autorun
Referências
- Web Vitals - Google Developers — Documentação oficial sobre Core Web Vitals, incluindo definições, métricas e boas práticas
- Optimize LCP - web.dev — Guia completo para otimização do Largest Contentful Paint com exemplos práticos
- Optimize CLS - web.dev — Estratégias detalhadas para eliminar mudanças de layout e melhorar o Cumulative Layout Shift
- Optimize INP - web.dev — Tutorial técnico sobre como melhorar o Interaction to Next Paint com técnicas de otimização do thread principal
- Lighthouse CI - GitHub — Ferramenta oficial do Google para automação de auditorias Lighthouse em pipelines de CI/CD
- Web Vitals API - MDN Web Docs — Referência técnica da API Performance Observer para coleta de métricas reais de usuários
- PageSpeed Insights — Ferramenta oficial do Google para análise de performance com recomendações específicas para Core Web Vitals