Otimização de desempenho
Otimização de desempenho baseada em evidências, usando sinais reais de usuários para priorização e traces de navegador para diagnóstico. Foca em velocidade de carregamento, capacidade de resposta em tempo de execução e entrega de recursos.
Como funciona
- Se uma página pode ser executada, leia o fluxo de trabalho de medição e estabeleça uma linha de base de campo e laboratório antes de editar.
- Priorize os Core Web Vitals ruins de usuários reais. Use um trace de desempenho do DevTools e seus insights focados para encontrar a causa.
- Inspecione e altere apenas o código ou os ativos conectados aos gargalos medidos.
- Reexecute medições de laboratório equivalentes e relate valores antes/depois, condições e incerteza. A verificação de campo permanece pendente até que novos dados de usuário cheguem.
Quando não existir uma página executável, realize inspeção estática, mas chame as descobertas de hipóteses, não de regressões medidas. Inclua o comando ou fluxo de trabalho do navegador que pode verificar cada hipótese de alto impacto.
Prefira uma ferramenta de navegador que grave um trace de desempenho e exponha insights focados. Com o Chrome DevTools MCP, use performance_start_trace e performance_analyze_insight; não roteie desempenho através de lighthouse_audit, que cobre categorias de Lighthouse que não são de desempenho.
Orçamento de desempenho inicial
Os orçamentos devem refletir os dispositivos, redes, tipos de página e jornadas de usuário alvo do produto. Os valores abaixo são diretrizes iniciais para uma página típica de conteúdo ou comércio, não critérios universais de aprovação/reprovação. Preserve um orçamento de projeto existente quando já estiver definido.
| Recurso | Orçamento | Justificativa |
|---|---|---|
| Peso total da página | < 1,5 MB | Limita o tempo de transferência e o custo de dados em redes alvo restritas; calibre com páginas representativas |
| JavaScript (comprimido) | < 300 KB | Protege o custo de análise e execução |
| CSS (comprimido) | < 100 KB | Limita o trabalho de bloqueio de renderização |
| Imagens (acima da dobra) | < 500 KB | Protege recursos prováveis de LCP |
| Fontes | < 100 KB | Limita a transferência crítica de fontes |
| Terceiros | < 200 KB | Limita o código fora do controle do produto |
Caminho crítico de renderização
Resposta do servidor
- TTFB < 800ms. O Time to First Byte deve ser rápido. Use CDN, cache e backends eficientes.
- Ative a compressão. Gzip ou Brotli para ativos de texto. Brotli é preferido (15-20% menor).
- HTTP/2 ou HTTP/3. Multiplexação reduz a sobrecarga de conexão.
- Cache de borda. Armazene HTML em cache na borda da CDN quando possível.
- Considere Early Hints (HTTP 103) para latência de documento medida. Se um trace mostrar geração lenta de HTML e sub-recursos críticos estáveis, envie um
103provisório com cabeçalhosLinkantes da resposta final normal da mesma solicitação. Use HTTP/2 ou posterior. Uma CDN pode sintetizar o103a partir de cabeçalhosLinkem um200anterior, ou o manipulador de origem/borda pode emiti-lo diretamente. Clientes não suportados continuam para a resposta final, mas confirme o suporte atual do navegador e da infraestrutura. Limite as dicas a pré-cargas ou pré-conexões críticas comprovadas: dicas imprecisas desperdiçam largura de banda. A Cloudflare relatou uma melhoria de 20-30% no LCP em um teste artificial e pesado em imagens; trate isso como um estudo de caso do fornecedor, não como uma economia esperada, e meça seu resultado. Veja o exemplo de implementação 103 do MDN e o estudo da Cloudflare.
Carregamento de recursos
Pré-conecte-se a origens necessárias:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
Pré-carregue recursos críticos:
Pré-carregue apenas recursos cuja descoberta tardia seja visível no trace. Cada pré-carga compete por largura de banda e uma solicitação desnecessária de alta prioridade pode atrasar o LCP.
<!-- Imagem LCP -->
<link rel="preload" href="/hero.webp" as="image" fetchpriority="high">
<!-- Fonte crítica -->
<link rel="preload" href="/font.woff2" as="font" type="font/woff2" crossorigin>
Pré-renderize prováveis próximas navegações com a Speculation Rules API:
<script type="speculationrules">
{
"prerender": [{
"where": { "href_matches": "/*" },
"eagerness": "moderate"
}]
}
</script>
moderate aguarda um sinal de intenção mais forte do que os modos eager. Meça a taxa de acerto de previsão, bytes transferidos e custo do servidor; uma pré-renderização errada é aproximadamente uma navegação não utilizada. Veja core-web-vitals → LCP para as compensações e a proteção prerenderingchange necessária para análises.
Adie CSS não crítico:
<!-- CSS crítico inline -->
<style>/* Estilos acima da dobra */</style>
<!-- CSS não crítico -->
<link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
Otimização de JavaScript
Adie scripts não essenciais:
<!-- Bloqueio de análise (evitar) -->
<script src="/critical.js"></script>
<!-- Adiado (preferido) -->
<script defer src="/app.js"></script>
<!-- Assíncrono (para scripts independentes) -->
<script async src="/analytics.js"></script>
<!-- Módulo (adiado por padrão) -->
<script type="module" src="/app.mjs"></script>
Padrões de divisão de código:
// Divisão baseada em rota
const Dashboard = lazy(() => import('./Dashboard'));
// Divisão baseada em componente
const HeavyChart = lazy(() => import('./HeavyChart'));
// Divisão baseada em funcionalidade
if (user.isPremium) {
const PremiumFeatures = await import('./PremiumFeatures');
}
Melhores práticas de tree shaking:
// ❌ Importa a biblioteca inteira
import _ from 'lodash';
_.debounce(fn, 300);
// ✅ Importa apenas o necessário
import debounce from 'lodash/debounce';
debounce(fn, 300);
Otimização de imagens
Seleção de formato
| Formato | Caso de uso | Suporte do navegador |
|---|---|---|
| AVIF | Fotos, melhor compressão | 92%+ |
| WebP | Fotos, boa alternativa | 97%+ |
| PNG | Gráficos com transparência | Universal |
| SVG | Ícones, logotipos, ilustrações | Universal |
Imagens responsivas
<picture>
<!-- AVIF para navegadores modernos -->
<source
type="image/avif"
srcset="hero-400.avif 400w,
hero-800.avif 800w,
hero-1200.avif 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<!-- WebP como alternativa -->
<source
type="image/webp"
srcset="hero-400.webp 400w,
hero-800.webp 800w,
hero-1200.webp 1200w"
sizes="(max-width: 600px) 100vw, 50vw">
<!-- JPEG como alternativa -->
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 50vw"
width="1200"
height="600"
alt="Imagem hero"
loading="lazy"
decoding="async">
</picture>
Prioridade da imagem LCP
<!-- Imagem LCP acima da dobra: carregamento eager, alta prioridade -->
<img
src="hero.webp"
fetchpriority="high"
loading="eager"
decoding="sync"
alt="Hero">
<!-- Imagens abaixo da dobra: carregamento lazy -->
<img
src="product.webp"
loading="lazy"
decoding="async"
alt="Produto">
Otimização de fontes
Estratégia de carregamento
/* Pilha de fontes do sistema como fallback */
body {
font-family: 'Custom Font', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, sans-serif;
}
/* Evitar texto invisível */
@font-face {
font-family: 'Custom Font';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap; /* ou optional para não críticas */
font-weight: 400;
font-style: normal;
unicode-range: U+0000-00FF; /* Subconjunto para Latim */
}
Pré-carregamento de fontes críticas
<link rel="preload" href="/fonts/heading.woff2" as="font" type="font/woff2" crossorigin>
Fontes variáveis
/* Um arquivo em vez de vários pesos */
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
Estratégia de cache
Cabeçalhos Cache-Control
# HTML (curto ou sem cache)
Cache-Control: no-cache, must-revalidate
# Ativos estáticos com hash (imutável)
Cache-Control: public, max-age=31536000, immutable
# Ativos estáticos sem hash
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
# Respostas de API
Cache-Control: private, max-age=0, must-revalidate
Cache do service worker
// Cache-first para ativos estáticos
self.addEventListener('fetch', (event) => {
if (event.request.destination === 'image' ||
event.request.destination === 'style' ||
event.request.destination === 'script') {
event.respondWith(
caches.match(event.request).then((cached) => {
return cached || fetch(event.request).then((response) => {
const clone = response.clone();
caches.open('static-v1').then((cache) => cache.put(event.request, clone));
return response;
});
})
);
}
});
Desempenho em tempo de execução
Evite thrashing de layout
// ❌ Força vários reflows
elements.forEach(el => {
const height = el.offsetHeight; // Leitura
el.style.height = height + 10 + 'px'; // Escrita
});
// ✅ Agrupa leituras, depois agrupa escritas
const heights = elements.map(el => el.offsetHeight); // Todas as leituras
elements.forEach((el, i) => {
el.style.height = heights[i] + 10 + 'px'; // Todas as escritas
});
Debounce de operações caras
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
// Debounce de manipuladores de scroll/redimensionamento
window.addEventListener('scroll', debounce(handleScroll, 100));
Use requestAnimationFrame
// ❌ Pode causar jank
setInterval(animate, 16);
// ✅ Sincronizado com a atualização do display
function animate() {
// Lógica de animação
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Virtualize listas longas
// Para listas > 100 itens, renderize apenas os itens visíveis
// Use bibliotecas como react-window, vue-virtual-scroller, ou CSS nativo:
.virtual-list {
content-visibility: auto;
contain-intrinsic-size: 0 50px; /* Altura estimada do item */
}
Navegações suaves com View Transitions
A View Transitions API permite que o navegador faça uma transição suave (ou animação personalizada) entre dois estados do DOM usando um único snapshot composto pela GPU — sem dupla renderização, sem thrashing de layout, e o snapshot não conta para CLS.
Mesmo documento (estilo SPA) — Baseline 2026:
// Envolva a mutação do DOM que troca a visualização
function navigate(newView) {
if (!document.startViewTransition) return swapDOM(newView);
document.startViewTransition(() => swapDOM(newView));
}
Documentos diferentes (estilo MPA) — Estável no Chromium, aprimoramento progressivo em outros:
/* Em ambas as páginas de origem e destino */
@view-transition { navigation: auto; }
Essa é a integração completa — as navegações de mesma origem agora desvanecem automaticamente. Para optar por elementos específicos em transições de elemento compartilhado (por exemplo, uma miniatura expandindo em um hero), dê a eles um view-transition-name correspondente:
.product-thumb[data-id="42"], .product-hero { view-transition-name: product-42; }
Combine isso com Speculation Rules (acima) para navegações instantâneas e animadas.
Scripts de terceiros
Estratégias de carregamento
// ❌ Bloqueia a thread principal
<script src="https://analytics.example.com/script.js"></script>
// ✅ Carregamento assíncrono
<script async src="https://analytics.example.com/script.js"></script>
// ✅ Atrasar até a interação
<script>
document.addEventListener('DOMContentLoaded', () => {
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
const script = document.createElement('script');
script.src = 'https://widget.example.com/embed.js';
document.body.appendChild(script);
observer.disconnect();
}
});
observer.observe(document.querySelector('#widget-container'));
});
</script>
Padrão de fachada
<!-- Mostrar placeholder estático até a interação -->
<div class="youtube-facade"
data-video-id="abc123"
onclick="loadYouTube(this)">
<img src="/thumbnails/abc123.jpg" alt="Título do vídeo">
<button aria-label="Reproduzir vídeo">▶</button>
</div>
Medição
Use o fluxo de trabalho de medição sempre que uma URL for executável. Ele define roteamento do Chrome DevTools MCP, fontes CrUX e fallback, condições de laboratório repetíveis e um formato de evidência compacto.
| Métrica | Tipo | Interpretação |
|---|---|---|
| LCP, INP, CLS no p75 | Campo | Core Web Vitals de resultado do usuário; use para priorização de aprovação/reprovação |
| LCP, CLS em um trace | Laboratório | Valores diagnósticos reproduzíveis para uma navegação |
| TBT | Laboratório | Diagnóstico de bloqueio da thread principal e um proxy aproximado de INP, não INP de campo |
| FCP, Speed Index | Laboratório | Diagnósticos de carregamento, não Core Web Vitals |
Snippets brutos de PerformanceObserver são úteis para a sessão atual do navegador, mas não são dados de usuário real por si só. Quando o usuário deseja telemetria de produção, leia a referência RUM de primeira parte e prefira web-vitals em vez de uma implementação de métrica feita à mão.
Referências
Para otimizações específicas de Core Web Vitals, veja Core Web Vitals.


