af aprenda frontend
módulo 08 horizonte

Performance web — rápido para quem importa.

Core Web Vitals (LCP, INP, CLS), lazy loading, code splitting, otimização de imagens, Lighthouse.

Performance não é sobre código elegante — é sobre o usuário. Uma página que demora 4 segundos para mostrar o conteúdo principal tem taxa de abandono significativamente maior que uma que mostra em 1 segundo. E performance piora à medida que projetos crescem — bibliotecas adicionadas, imagens não otimizadas, JavaScript que bloqueia o render. Esta lição cobre o que medir, o que otimizar, e como.

Core Web Vitals — as métricas que importam

Google define três métricas principais de experiência do usuário — os Core Web Vitals. São usadas como fator de ranking na busca e como benchmarks de qualidade:

LCP — Largest Contentful Paint. Quanto tempo até o maior elemento visível na viewport ter carregado — geralmente a imagem de capa do artigo ou o <h1> principal. Meta: abaixo de 2,5 segundos. O LCP é a métrica mais próxima de “quando a página parece útil” para o usuário.

INP — Interaction to Next Paint. Quanto tempo entre uma interação do usuário (clicar, digitar, pressionar tecla) e a resposta visual. Meta: abaixo de 200ms. Mede se a página responde de forma ágil às interações.

CLS — Cumulative Layout Shift. Soma de todos os deslocamentos de layout inesperados — elementos que pulam quando imagens carregam ou fontes mudam. Meta: abaixo de 0,1. Um CLS alto faz o usuário clicar no botão errado quando o layout muda de repente.

Medir com Lighthouse

Lighthouse é a ferramenta de auditoria integrada ao Chrome DevTools. Abre qualquer página no Chrome, abre DevTools (F12), vai na aba Lighthouse, e clica em “Analyze page load”:

O relatório mostra pontuação de 0 a 100 em Performance, Acessibilidade, Boas Práticas e SEO, com diagnósticos específicos do que está causando problemas. Cada item tem um link para a documentação explicando o que fazer.

Para o blog, os problemas mais comuns:

  • Imagens sem width e height — causa CLS
  • Imagens em formato JPEG/PNG em vez de WebP — aumenta LCP
  • Bundle JavaScript grande — atrasa o LCP
  • Fonts sem font-display: swap — bloqueia o render

Imagens — o maior impacto de performance

Imagens são frequentemente a maior parte do peso de uma página web. Três otimizações que têm impacto imediato:

Formato correto. WebP oferece compressão 25–35% melhor que JPEG com qualidade visual equivalente. AVIF é ainda melhor (~50% menor). Use WebP como padrão, com AVIF quando o suporte ao browser não for uma restrição:

html
<picture>
  <source srcset="capa.avif" type="image/avif" />
  <source srcset="capa.webp" type="image/webp" />
  <img src="capa.jpg" alt="Ilustração do artigo CSS Grid" width="800" height="450" />
</picture>
picture com fallback — WebP com JPEG para browsers antigos.

Tamanho correto. Não servir uma imagem de 2000px para um container de 400px. Use o atributo srcset para servir tamanhos diferentes em viewports diferentes:

html
<img
  src="capa-800.webp"
  srcset="capa-400.webp 400w, capa-800.webp 800w, capa-1600.webp 1600w"
  sizes="(max-width: 640px) 400px, (max-width: 1280px) 800px, 1600px"
  alt="Ilustração do artigo CSS Grid"
  width="800"
  height="450"
/>
srcset — imagem responsiva com tamanhos corretos.

Lazy loading. Imagens abaixo da viewport não precisam carregar imediatamente:

html
<!-- imagem no topo da página — não usar lazy -->
<img src="capa.webp" alt="..." width="800" height="450" />

<!-- imagens em ArticleCards na lista — usar lazy -->
<img src="thumb.webp" alt="..." loading="lazy" width="400" height="225" />
loading='lazy' — adiar imagens fora da viewport.

loading="lazy" é suportado por todos os browsers modernos e é a otimização mais fácil de implementar.

Code splitting — carregar só o necessário

O bundle do blog React contém todo o JavaScript da aplicação. Se ArticlePage é um componente grande que 90% dos usuários não vão abrir na mesma sessão, faz sentido carregá-lo apenas quando necessário:

tsx
import { lazy, Suspense } from "react";

// ArticlePage é carregado apenas quando o usuário navega para o artigo
const ArticlePage = lazy(() => import("./pages/ArticlePage"));
const HomePage = lazy(() => import("./pages/HomePage"));

function App() {
  return (
    <Suspense fallback={<div>Carregando...</div>}>
      {/* roteamento simplificado — use React Router na prática */}
      {location.pathname === "/" ? <HomePage /> : <ArticlePage />}
    </Suspense>
  );
}
React.lazy + Suspense — code splitting automático.

Vite gera chunks separados para cada lazy() — o browser só baixa o JavaScript de ArticlePage quando o usuário navegar para um artigo.

Otimização do JavaScript

Bundle analyzer. Antes de otimizar, saiba o que está grande:

bash
npx vite-bundle-visualizer
# ou
npx source-map-explorer dist/assets/*.js
Analisar o bundle gerado.

O resultado é um mapa visual dos módulos no bundle — fácil de identificar bibliotecas que pesam mais do que deveriam.

Problemas comuns:

  • import * as icons from "lucide-react" — importa todos os ícones (centenas). Use import { ChevronDown } from "lucide-react" — apenas o que precisa
  • import _ from "lodash" — importa a biblioteca inteira. Use import debounce from "lodash/debounce" ou uma alternativa menor
  • Bibliotecas duplicadas — versões diferentes de uma mesma biblioteca no bundle

Fonts — evitar flash de texto sem estilo

Fonts carregadas do Google Fonts bloqueiam a renderização por padrão. Duas estratégias:

html
<!-- preconnect — estabelecer a conexão antecipadamente -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

<!-- display=swap — renderizar com font de fallback enquanto carrega -->
<link
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
  rel="stylesheet"
/>
Preconnect + swap — fonts do Google sem bloquear o render.

display=swap instrui o browser a usar a font de sistema enquanto a custom font carrega — sem texto invisível, mas pode haver um pequeno flash quando a font troca.

O que priorizar

Performance tem rendimentos decrescentes — as primeiras otimizações têm impacto enorme, as últimas são micro-ajustes. Ordem de prioridade para o blog:

  1. Imagens em WebP com loading="lazy" e dimensões corretas — maior impacto, mais fácil
  2. font-display: swap — elimina blocking de render por fonts
  3. Code splitting com React.lazy — carrega só o necessário
  4. Bundle analyzer + remover dependências pesadas — quando o bundle ultrapassa ~200KB gzip

Resumo

  • Core Web Vitals: LCP (maior elemento visível < 2.5s), INP (resposta a interações < 200ms), CLS (deslocamentos de layout < 0.1).
  • Lighthouse: audita LCP, INP, CLS e dá diagnósticos com ações concretas.
  • Imagens: WebP/AVIF para formato, srcset para tamanho certo por viewport, loading="lazy" para imagens fora da fold. Sempre definir width e height.
  • Code splitting: React.lazy() + Suspense carrega componentes sob demanda — Vite gera chunks automáticos.
  • Bundle: analisar com vite-bundle-visualizer. Importações granulares em vez de importar a biblioteca inteira.
  • Fonts: preconnect + display=swap elimina o blocking de render de fontes externas.
/ checkpoint verifique seu entendimento
questão 1 de 4

O que o LCP (Largest Contentful Paint) mede?