Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: otimizar description: Otimiza a performance da aplicação. Use quando o usuário tiver requisitos de performance, suspeitar de regressão, precisar melhorar Core Web Vitals ou tempos de carregamento, ou quando o profiling revelar gargalos a corrigir. metadata: fase: revisar origem: addyosmani/agent-skills (MIT)
Otimizar — Performance
Visão geral
Meça antes de otimizar. Trabalho de performance sem medição é chute — e chute leva a otimização prematura, que adiciona complexidade sem melhorar o que importa. Faça profiling primeiro, identifique o gargalo real, corrija, meça de novo. Otimize apenas o que as medições provarem que importa.
Quando usar
- Existem requisitos de performance na spec (orçamento de tempo de carga, SLA de resposta)
- Usuários ou monitoramento reportam lentidão
- Core Web Vitals abaixo dos limiares
- Suspeita de que uma mudança introduziu regressão
- Funcionalidades que lidam com grandes volumes de dados ou tráfego alto
Quando NÃO usar: não otimize antes de ter evidência de problema. Otimização prematura adiciona complexidade que custa mais do que a performance que entrega.
Metas de Core Web Vitals
| Métrica | Boa | Precisa melhorar | Ruim | |
|---|---|---|---|---|
| LCP (Largest Contentful Paint) | ≤ 2,5 s | ≤ 4,0 s | > 4,0 s | |
| INP (Interaction to Next Paint) | ≤ 200 ms | ≤ 500 ms | > 500 ms | |
| CLS (Cumulative Layout Shift) | ≤ 0,1 | ≤ 0,25 | > 0,25 |
O fluxo
1. MEDIR → Estabeleça a linha de base com dados reais2. IDENTIFICAR → Encontre o gargalo real (não o suposto)3. CORRIGIR → Ataque o gargalo específico4. VERIFICAR → Meça de novo, confirme a melhoria5. PROTEGER → Adicione monitoramento ou testes contra regressão
Etapa 1: Medir
Duas abordagens complementares — use ambas:
- Sintética (Lighthouse, aba Performance do DevTools): condições controladas, reproduzível. Melhor para detectar regressões em CI e isolar problemas específicos.
- RUM (biblioteca web-vitals, CrUX): dados de usuários reais em condições reais. Obrigatória para validar que uma correção de fato melhorou a experiência.
// RUM: web-vitals no códigoimport { onLCP, onINP, onCLS } from 'web-vitals';onLCP(console.log);onINP(console.log);onCLS(console.log);// Backend: medição simples de tempoconsole.time('db-query');const result = await db.query(...);console.timeEnd('db-query');
Use o sintoma para decidir o que medir primeiro:
- Primeiro carregamento lento → tamanho do bundle, TTFB no waterfall de rede, recursos que bloqueiam renderização
- Interação travada → long tasks (>50 ms) na main thread, re-renders, layout thrashing
- Página lenta após navegação → tempos de resposta da API, waterfalls de fetch, N+1 no cliente
- Backend/API lento → queries e índices (um endpoint); pool de conexões, memória, CPU (todos); locks, pausas de GC, dependências externas (intermitente)
A árvore de diagnóstico completa (incluindo diagnóstico de TTFB) está em referencias/performance.md — leia somente quando chegar nesta etapa.
Etapa 2: Identificar o gargalo
| Sintoma | Causa provável | Investigação | |
|---|---|---|---|
| LCP lento | Imagens grandes, recursos bloqueantes, servidor lento | Waterfall de rede, tamanho das imagens | |
| CLS alto | Imagens sem dimensões, conteúdo tardio, troca de fontes | Atribuição de layout shift | |
| INP ruim | JavaScript pesado na main thread, grandes atualizações de DOM | Long tasks no trace de Performance | |
| API lenta | Queries N+1, índices ausentes, queries não otimizadas | Log de queries do banco | |
| Memória crescendo | Referências vazadas, caches sem limite, payloads grandes | Análise de heap snapshot | |
| Picos de CPU | Computação pesada síncrona, backtracking de regex | Profiling de CPU |
Etapa 3: Corrigir os antipadrões
Os dois mais comuns e de maior impacto:
// RUIM: N+1 — uma query por tarefa para buscar o donoconst tasks = await db.tasks.findMany();for (const task of tasks) {task.owner = await db.users.findUnique({ where: { id: task.ownerId } });}// BOM: uma única query com join/includeconst tasks = await db.tasks.findMany({ include: { owner: true } });
// RUIM: buscar todos os registrosconst allTasks = await db.tasks.findMany();// BOM: paginado com limiteconst tasks = await db.tasks.findMany({take: 20,skip: (page - 1) * 20,orderBy: { createdAt: 'desc' },});
No frontend, os campeões de impacto são: imagens sem otimização (dimensões explícitas, formatos modernos, srcset, loading="lazy", fetchpriority="high" no LCP), re-renders desnecessários no React (referências estáveis, React.memo/useMemo apenas onde o profiling mostrar ganho) e bundle grande (code splitting com lazy() + Suspense por rota e para funcionalidades pesadas).
Os exemplos completos — imagem hero responsiva com art direction, caching de backend e HTTP, padrões de React — estão em referencias/performance.md.
Etapa 4: Verificar
Meça de novo nas mesmas condições da linha de base. Sem números de antes e depois, a otimização não aconteceu — foi só uma mudança de código.
Etapa 5: Proteger com orçamento de performance
Defina orçamentos e imponha-os no CI:
Bundle JavaScript: < 200 KB gzip (carga inicial)CSS: < 50 KB gzipImagens: < 200 KB por imagem (acima da dobra)Fontes: < 100 KB no totalTempo de resposta da API: < 200 ms (p95)Time to Interactive: < 3,5 s em 4GLighthouse Performance: ≥ 90
# Checagem de tamanho de bundlenpx bundlesize --config bundlesize.config.json# Lighthouse CInpx lhci autorun
Racionalizações comuns
| Racionalização | Realidade | |
|---|---|---|
| "Otimizamos depois" | Dívida de performance se acumula. Corrija antipadrões óbvios agora, adie micro-otimizações. | |
| "Na minha máquina é rápido" | Sua máquina não é a do usuário. Faça profiling em hardware e redes representativos. | |
| "Essa otimização é óbvia" | Se você não mediu, não sabe. Profiling primeiro. | |
| "Usuário não percebe 100 ms" | Pesquisas mostram que atrasos de 100 ms impactam conversão. Usuários percebem mais do que você imagina. | |
| "O framework cuida da performance" | Frameworks previnem alguns problemas, mas não consertam N+1 nem bundles inchados. |
Sinais de alerta
- Otimização sem dados de profiling que a justifiquem
- Padrões de query N+1 na busca de dados
- Endpoints de listagem sem paginação
- Imagens sem dimensões, lazy loading ou tamanhos responsivos
- Tamanho de bundle crescendo sem revisão
- Sem monitoramento de performance em produção
React.memoeuseMemoem todo lugar (excesso é tão ruim quanto falta)
Portão de aprovação
Apresente: as medições de antes e depois (números concretos), o gargalo identificado, a correção aplicada e qualquer complexidade adicional introduzida (cache, memoização, code splitting). O humano aprova: os trade-offs de complexidade versus ganho, mudanças no orçamento de performance e a decisão de adiar otimizações restantes. Só avance após aprovação explícita.
Verificação
Após qualquer mudança relacionada a performance:
- [ ] Existem medições de antes e depois (números específicos)
- [ ] O gargalo específico foi identificado e atacado
- [ ] Core Web Vitals dentro dos limiares "Bons"
- [ ] O tamanho do bundle não cresceu significativamente
- [ ] Sem queries N+1 no novo código de busca de dados
- [ ] Orçamento de performance passa no CI (se configurado)
- [ ] Testes existentes continuam passando (a otimização não quebrou comportamento)