Critical Rendering Path otimizado
TL;DR
O Critical Rendering Path (CRP) é a sequência de passos do browser do HTML recebido até o primeiro pixel pintado. Otimizar CRP significa: entregar o CSS crítico inline (elimina round-trip de rede), carregar CSS não-crítico de forma assíncrona, usar
defer/asyncem scripts, e usar resource hints (preload,preconnect) para antecipar downloads. O objetivo final é minimizar o LCP (Largest Contentful Paint) — a métrica de velocidade percebida mais importante.
O Critical Rendering Path
Receber HTML
↓
Parsear HTML → DOM
↓ (em paralelo)
Parsear CSS → CSSOM
↓
Combine → Render Tree
↓
Layout
↓
Paint → First Paint / First Contentful Paint
↓
LCP (Largest Contentful Paint)
O “caminho crítico” são os recursos que bloqueiam este fluxo: CSS e scripts síncronos são os principais.
CSS crítico inline
O CSS “crítico” é o CSS necessário para renderizar o conteúdo above the fold (visível sem scroll). Inlinar no <head> elimina um round-trip de rede:
<!DOCTYPE html>
<html>
<head>
<!-- ✅ CSS crítico inline — zero round-trip, sem render blocking -->
<style>
/* Apenas o CSS acima da dobra */
body { margin: 0; font-family: system-ui, sans-serif; }
.header { background: #0077cc; color: white; padding: 1rem; }
.hero { min-height: 60vh; display: flex; align-items: center; }
.hero__title { font-size: 2.5rem; margin: 0; }
</style>
<!-- CSS não-crítico carregado de forma assíncrona -->
<link
rel="preload"
href="/static/styles.css"
as="style"
onload="this.rel='stylesheet'"
>
<noscript><link rel="stylesheet" href="/static/styles.css"></noscript>
</head>O truque do preload + onload:
rel="preload"baixa o arquivo com alta prioridade sem bloquear o renderonload="this.rel='stylesheet'"aplica o CSS quando termina de baixar<noscript>garante que usuários sem JS recebem o CSS normalmente
Ferramentas para extrair CSS crítico
# critical — extrai CSS above-the-fold para um viewport específico
npx critical public/index.html --width 1300 --height 900 --inline
# PurgeCSS — remove CSS não utilizado antes de extrair o críticoNa prática para apps React/Next.js:
- Next.js faz isso automaticamente com CSS Modules
- Para styled-components/Emotion, o servidor extrai o CSS das rotas renderizadas
Resource hints — antecipar downloads
<head>
<!-- preconnect: estabelecer TCP + TLS com a origem antecipadamente -->
<!-- Use para origens que você sabe que vai usar (fonts, CDN, APIs) -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- dns-prefetch: só resolve DNS (mais barato que preconnect) -->
<!-- Use para origens de terceiros que podem não ser usadas -->
<link rel="dns-prefetch" href="https://analytics.example.com">
<!-- preload: baixar recurso crítico com alta prioridade -->
<!-- Use para: LCP image, fonte, script crítico -->
<link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin>
<link rel="preload" href="/hero.jpg" as="image">
<link rel="preload" href="/critical-data.json" as="fetch" crossorigin>
<!-- prefetch: baixar recurso de baixa prioridade para próxima navegação -->
<link rel="prefetch" href="/about.js">
<link rel="prefetch" href="/about.css">
<!-- modulepreload: preload de ES modules -->
<link rel="modulepreload" href="/src/main.js">
<link rel="modulepreload" href="/src/router.js"> <!-- imports de main.js -->
</head>fetchpriority — ajustar prioridade de carregamento
O browser atribui prioridades automaticamente, mas você pode ajustá-las:
<!-- LCP image — alta prioridade (browser às vezes trata como baixa por estar em img lazy) -->
<img
src="/hero.jpg"
alt="Hero"
width="1200"
height="600"
fetchpriority="high"
>
<!-- Imagem below the fold — baixa prioridade + lazy loading -->
<img
src="/feature.jpg"
alt="Feature"
loading="lazy"
fetchpriority="low"
decoding="async"
>
<!-- Script de análise não crítico -->
<script src="analytics.js" async fetchpriority="low"></script>Imagens — as maiores oportunidades de CRP
<!-- ✅ Dimensões explícitas — evita CLS (layout shift quando a imagem carrega) -->
<img src="product.jpg" alt="Produto" width="800" height="600">
<!-- ✅ Lazy loading para imagens below the fold -->
<img src="product.jpg" alt="Produto" loading="lazy" decoding="async">
<!-- ✅ Imagem LCP — sem lazy, com preload, com fetchpriority -->
<img src="hero.jpg" alt="Hero" fetchpriority="high" decoding="sync">
<!-- ✅ Responsive images — o browser escolhe o tamanho certo -->
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w, hero-800.jpg 800w, hero-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 900px) 800px, 1200px"
alt="Hero"
fetchpriority="high"
>
<!-- ✅ Formato moderno — WebP/AVIF com fallback -->
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero">
</picture>Core Web Vitals — as métricas que importam
| Métrica | O que mede | Bom | Ruim |
|---|---|---|---|
| LCP (Largest Contentful Paint) | Velocidade de carregamento percebida | < 2.5s | > 4s |
| CLS (Cumulative Layout Shift) | Estabilidade visual | < 0.1 | > 0.25 |
| INP (Interaction to Next Paint) | Responsividade a interação | < 200ms | > 500ms |
Otimizar LCP
O LCP element é tipicamente um <img>, <video>, ou bloco grande de texto. Para melhorar:
<!-- 1. Identificar o LCP element (DevTools → Performance → LCP) -->
<!-- 2. Garantir que é carregado com alta prioridade -->
<img src="hero.jpg" fetchpriority="high" alt="Hero">
<!-- 3. Preload se necessário -->
<link rel="preload" href="hero.jpg" as="image">
<!-- 4. Hosting próximo ao usuário (CDN) -->
<!-- 5. Formato eficiente (WebP/AVIF) -->
<!-- 6. Tamanho adequado (srcset responsivo) -->Evitar CLS
<!-- ✅ Dimensões explícitas em imagens -->
<img src="img.jpg" width="400" height="300" alt="">
<!-- ✅ Reservar espaço para anúncios e embeds -->
<div style="min-height: 250px;">
<!-- conteúdo dinâmico -->
</div>/* ✅ Fontes customizadas — evitar FOUT que causa shift */
@font-face {
font-display: swap; /* exibe fallback imediatamente, troca quando a fonte carrega */
/* Alternativa: optional — usa a fonte só se já estiver no cache */
}
/* ✅ Reservar espaço para conteúdo de tamanho variável */
.skeleton { min-height: 200px; }Melhorar INP
// INP = tempo entre interação do usuário e próximo paint
// ✅ Usar requestAnimationFrame para agendar updates visuais
btn.addEventListener('click', () => {
// Processamento rápido (< 50ms) no handler
const data = processInput();
// Visual na rAF — garante que acontece no próximo frame
requestAnimationFrame(() => {
updateUI(data);
});
});
// ✅ Para trabalho pesado (> 50ms), usar setTimeout para não bloquear o frame atual
btn.addEventListener('click', () => {
updateUIImmediately(); // feedback visual imediato
setTimeout(() => {
heavyProcessing(); // trabalho pesado depois
}, 0);
});Para fixar
- O que é o Critical Rendering Path? Quais recursos o bloqueiam?
- Como você carregaria CSS não-crítico de forma assíncrona? Escreva o HTML completo.
- Qual a diferença entre
preloadeprefetch? Quando usar cada um?- O que é CLS? Como dimensões explícitas em imagens evitam CLS?
- O que LCP mede? Se o LCP da sua página é 4s, quais são as principais causas prováveis?
Veja também
- 04 — Reflow e repaint — anterior
- 06 — requestAnimationFrame — próxima
- CSS 12 — Performance CSS — critical CSS pelo ângulo do CSS