Profiling: Entendendo o Desempenho Real
O profiling é a base de qualquer otimização séria. Não podemos melhorar aquilo que não medimos. A maioria dos desenvolvedores tenta otimizar por intuição, mas isso é perigoso — o gargalo real está sempre onde menos esperamos. O Chrome DevTools oferece ferramentas nativas excelentes para isso.
Comece abrindo a aba Performance no DevTools (F12 > Performance). Clique em "Record", interaja com sua página e pare a gravação. Você verá um gráfico detalhado mostrando CPU, memória, FPS e a timeline exata de cada operação. A métrica crucial aqui é Long Tasks — qualquer operação que ocupa mais de 50ms bloqueia a thread principal, travando a experiência do usuário.
// Exemplo: Identificar código lento
console.time('processamento');
function processarDados(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
total += items[i] * 2; // operação pesada
}
return total;
}
const dados = Array.from({ length: 1000000 }, (_, i) => i);
processarDados(dados);
console.timeEnd('processamento');
// Output: processamento: 2.5ms
Use também performance.mark() e performance.measure() para medir seções específicas do seu código:
performance.mark('inicio-fetch');
fetch('/api/dados')
.then(r => r.json())
.then(data => {
performance.mark('fim-fetch');
performance.measure('tempo-fetch', 'inicio-fetch', 'fim-fetch');
console.log(performance.getEntriesByName('tempo-fetch')[0].duration);
});
Lazy Loading: Carregando Apenas o Necessário
Lazy loading é uma estratégia simples mas poderosa: não carregue recursos que o usuário ainda não viu. Para imagens, o navegador moderno oferece o atributo nativo loading="lazy". Para JavaScript e componentes complexos, você precisa de lógica mais sofisticada.
A Intersection Observer API é a forma moderna e eficiente de detectar quando um elemento entra no viewport:
// Lazy loading nativo para imagens
<img src="placeholder.jpg"
loading="lazy"
data-src="imagem-real.jpg"
alt="descrição">
// Lazy loading programático com Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.addEventListener('load', () => {
img.classList.add('loaded');
});
observer.unobserve(img);
}
});
}, {
rootMargin: '50px' // começa a carregar 50px antes de aparecer
});
document.querySelectorAll('[data-src]').forEach(img => {
observer.observe(img);
});
Para componentes JavaScript, use dynamic imports. Isso permite que você carregue módulos apenas quando necessário:
// Módulo pesado carregado sob demanda
button.addEventListener('click', async () => {
const { iniciarModal } = await import('./modal-pesado.js');
iniciarModal();
});
Combine lazy loading com code splitting em bundlers como Webpack ou Vite para resultar em chunks menores no carregamento inicial. A primeira impressão é crítica.
Web Vitals: As Métricas que Importam
Google definiu três Core Web Vitals que afetam diretamente seu ranking e experiência do usuário. Estas são as métricas que você deve monitorar obsessivamente.
Largest Contentful Paint (LCP)
O tempo até o maior elemento visível carregar. Objetivo: < 2.5s. Otimize imagens, minimize CSS crítico e use CDN. Um LCP ruim geralmente indica servidor lento ou assets não otimizados.
First Input Delay (FID) / Interaction to Next Paint (INP)
O tempo de resposta do navegador à primeira interação do usuário. Objetivo: < 100ms. Reduza JavaScript bloqueante usando requestIdleCallback() ou web workers para operações pesadas:
// Trabalho pesado em thread separada
const worker = new Worker('processador.js');
button.addEventListener('click', () => {
worker.postMessage({ dados: largosArrays });
worker.onmessage = (event) => {
console.log('Resultado processado:', event.data);
};
});
// processador.js
self.onmessage = (event) => {
const resultado = event.data.dados.map(x => x * 2);
self.postMessage(resultado);
};
Cumulative Layout Shift (CLS)
Mudanças inesperadas no layout enquanto o usuário lê. Objetivo: < 0.1. Reserve espaço para imagens e anúncios:
<!-- ❌ Ruim: sem dimensões, layout shift quando carrega -->
<img src="grande.jpg">
<!-- ✅ Bom: dimensões preservadas -->
<img src="grande.jpg" width="800" height="600" alt="descrição">
<!-- Melhor ainda: aspect-ratio -->
<img src="grande.jpg"
style="aspect-ratio: 800/600;"
alt="descrição">
Meça Web Vitals com a biblioteca oficial do Google:
import { getCLS, getFID, getFCP, getLCP } from 'web-vitals';
getCLS(console.log); // CLS
getFID(console.log); // FID
getFCP(console.log); // FCP
getLCP(console.log); // LCP
// Enviar para seu analytics
getLCP(metric => {
fetch('/analytics', {
method: 'POST',
body: JSON.stringify(metric)
});
});
Conclusão
Dominar performance em JavaScript envolve três pilares: medir antes de otimizar (profiling), carregar recursos sob demanda (lazy loading) e rastrear as métricas que importam (Web Vitals). Comece pelo Chrome DevTools para identificar gargalos reais, implemente lazy loading onde faz sentido (imagens, componentes pesados) e monitore continuamente seus Web Vitals em produção. A diferença entre um site rápido e um lento não é complexidade técnica — é rigor na medição e disciplina na otimização.