Teu progresso
0 / 83 módulos0%
Estágio 03 · 03-09
BloqueadoPerformance front é onde mais devs se enganam. "Está rápido na minha máquina", em MacBook M3 com fibra. Em Moto G4 com 3G, sua landing page é 14s. Lighthouse 65. Conversões caem. Você descobre quando alguém do produto reclama. Aí começa cargo cult: lazy load tudo, code split tudo, React.memo em todo componente, sem entender o que está custando.
Este módulo é perf front com método: Web Vitals (LCP, INP, CLS), Network waterfall, JS bundle, render path, hydration cost, image/font loading, caching, edge. Você sai sabendo medir, identificar bottleneck verdadeiro, e otimizar com prioridade. 03-10 cobre backend perf.
Métricas Google que padronizam UX perf:
Métricas adicionais úteis:
Coleta:
Field > lab pra decisão. Lab pra detectar regressões em PR.
Página HTML chega → parser começa → encontra CSS → bloqueia render até CSS chegar. Encontra JS sync → bloqueia parser. Encontra <img> → fetch paralelo. Encontra <script defer> → fetch paralelo, executa após HTML done. <script async> → fetch paralelo, executa quando chegar (pode bloquear parser).
Critical render path:
Em SSR (Next), HTML chega completo; CSS in-head; user vê algo. Em SPA puro CSR, HTML é shell vazio + bundle JS gigante; user espera JS executar.
Otimização:
<link rel="preload" as="font">).loading="lazy").JS é caro: download, parse, compile, execute, GC. Em Moto G4, parse de 500 KB JS leva segundos.
Reduzir:
import('./heavy').then(...) carrega on-demand.@babel/preset-env com targets certo.next/bundle-analyzer, vite-bundle-visualizer.Meta: bundle inicial JS ≤ 100-150 KB gzipped pra apps normais. Acima disso, justificar.
memo, useMemo, useCallback: skipam recálculos. Não use cego, só onde profile mostra valor.useTransition (React 18+): marca update como não-urgente, pode ser interrompido por urgentes.useDeferredValue: defer derived value.react-window, tanstack/virtual.React DevTools Profiler: identifica componentes lentos, re-renders desnecessários.
RSC (React Server Components) move render pro servidor: client recebe HTML + RSC payload (sem JS pra esses componentes). Reduz bundle.
Edge runtime executa perto do user: TTFB baixo. Pra rotas que não dependem de DB pesado, edge ganha.
Static generation (SSG) > SSR > CSR em TTFB e LCP, when possible. Cache HTML no CDN.
Edge é a tendência dominante 2024-2026 pra UX-critical paths. Plataformas usam V8 isolates (não containers Lambda-style) — runtime sandboxado de ~5MB que sobe em ~5-50ms cold (vs Lambda Node ~200-1000ms cold).
Players principais:
| Plataforma | Runtime | Limite CPU/req | Cold start | Pricing model | Forte em |
|---|---|---|---|---|---|
| Cloudflare Workers | V8 isolate (workerd) | 50ms (Bundled) / 30s (Unbound) | < 5ms | Por requisição + duration | Edge global, KV, R2, D1 |
| Vercel Edge Functions | Vercel Edge Runtime (V8) | ~30s | ~10-50ms | Por GB-hour + invocations | Integração Next.js |
| Deno Deploy | Deno (V8 + Rust) | 50ms-isolate | ~10ms | Por request + GB | Standards-aligned, Web APIs |
| Bun edge runtimes (emerging) | Bun (JSC) | varia | ~ms | Provider-dependent | Throughput, npm compat |
| AWS Lambda@Edge / CloudFront Functions | Node container / V8 | 5s / 1ms | 100-1000ms / sub-ms | AWS pricing | AWS-native, custom logic |
Constraints reais que mordem:
fs, child_process, native modules não funcionam. Use Web Standards (fetch, crypto.subtle, URL, Streams).Quando edge ganha:
Quando edge perde:
Padrão arquitetural Logística: edge handle auth + rate limit + i18n routing, encaminha pra origin (Railway/Fastify) só pra rotas dinâmicas com DB. Pages estáticas + API leve em edge; transactional API em origin. Latência p99 cai pra metade ou menos em users distantes do origin region.
Cruza com 02-05 (Next.js routing edge runtime), 02-07 (porque Node APIs não estão lá), 04-09 (latência ao escalar).
Após HTML render, React precisa "hidratar", anexar event listeners. Bundle JS executa em main thread. Em apps grandes, hydration de página inteira trava interação por segundos.
Soluções:
Geralmente o gargalo de LCP.
srcset + sizes.<img> com width e height (evita CLS).Pra hero image: preload (<link rel="preload" as="image" imagesrcset="...">).
font-display: swap evita invisibilidade durante load.content-visibility: auto: skip render off-screen.will-change: hint pra browser otimizar.<link rel="preconnect" href="https://api.example.com">).Cache-Control properly. Hash em filenames pra cache busting.Cache-Control: public, max-age=31536000, immutable pra assets com hash em nome.Cache-Control: no-cache pra HTML, força revalidate, ETag pra 304.Analytics, chat, ads, A/B test scripts são frequentemente os maiores ofensores de perf.
async ou defer.INP = pior interação. Long tasks (> 50ms) bloqueiam main thread, pioram INP.
Detecte:
PerformanceObserver).Mitigações:
requestIdleCallback, scheduler.postTask).useTransition pra updates não-urgentes.{ passive: true }).Definir hard limits:
CI gate: build falha se exceder. Tools: bundlesize, size-limit, Lighthouse CI.
Lighthouse roda 1 user em 1 cenário. Não é verdade absoluta. Field data sempre prevalece. Mas Lighthouse pega regressões em PR.
Lighthouse CI: roda em PR, falha em regression de score.
Em projeto crítico de perf (landing pages, content sites), avaliar.
Lighthouse/PageSpeed mede em lab (single device, single network). Real User Monitoring (RUM) mede no campo — devices reais, network reais, edge cases reais. Sem RUM, time otimiza pra Lighthouse mas users em Android low-end com 3G veem 12s de LCP enquanto dashboard mostra "all green". §2.19 cobre stack RUM moderna 2026: web-vitals lib v4 com INP correto, attribution data, sampling, custos reais, integração com OTel/Sentry/Datadog/SpeedCurve.
| Metric | What | Good | Needs improvement | Poor |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | ≤ 2.5s | 2.5-4s | > 4s |
| INP | Interaction to Next Paint (replaced FID em 2024) | ≤ 200ms | 200-500ms | > 500ms |
| CLS | Cumulative Layout Shift | ≤ 0.1 | 0.1-0.25 | > 0.25 |
| FCP | First Contentful Paint (diagnostic) | ≤ 1.8s | 1.8-3s | > 3s |
| TTFB | Time to First Byte (diagnostic) | ≤ 800ms | 800ms-1.8s | > 1.8s |
// src/lib/rum.ts
import { onLCP, onINP, onCLS, onFCP, onTTFB, type Metric } from 'web-vitals';
type AttributedMetric = Metric & {
attribution: Record<string, unknown>;
};
function sendToAnalytics(metric: AttributedMetric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id,
navigationType: metric.navigationType,
page: location.pathname,
attribution: metric.attribution,
device: {
deviceMemory: (navigator as any).deviceMemory,
hardwareConcurrency: navigator.hardwareConcurrency,
connection: (navigator as any).connection?.effectiveType,
},
userId: window.__userId,
sessionId: window.__sessionId,
});
// sendBeacon não bloqueia unload; fallback fetch keepalive
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/rum', body);
} else {
fetch('/api/rum', { body, method: 'POST', keepalive: true });
}
}
export function initRum() {
onLCP(sendToAnalytics, { reportAllChanges: false });
onINP(sendToAnalytics, { reportAllChanges: false });
onCLS(sendToAnalytics, { reportAllChanges: false });
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
}
reportAllChanges: false (default): só reporta valor final no page hide / unload. true reporta cada update — útil em dashboards real-time mas inflate volume 10x.sendBeacon: API designed pra envio em unload sem bloquear navigation. Limite 64KB por call.web-vitals v4 inclui attribution por métrica:
onLCP((metric) => {
// metric.attribution.element: HTMLElement do LCP
// metric.attribution.url: src/href
// metric.attribution.timeToFirstByte: TTFB sub-component
// metric.attribution.resourceLoadDelay: tempo entre FCP e início load
// metric.attribution.resourceLoadDuration: download time
// metric.attribution.elementRenderDelay: tempo de download a render
});
onINP((metric) => {
// metric.attribution.eventTarget: HTMLElement clicado
// metric.attribution.eventType: 'click' | 'pointerdown' | 'keydown' | ...
// metric.attribution.inputDelay: entre input e processing start
// metric.attribution.processingDuration: handler runtime
// metric.attribution.presentationDelay: entre handler end e paint
// metric.attribution.longestScript: script src + duration > 50ms
});
onCLS((metric) => {
// metric.attribution.largestShiftTarget: HTMLElement do shift maior
// metric.attribution.largestShiftTime, largestShiftValue
// metric.attribution.loadState: pre/post load
});
Sem attribution: dashboard mostra "INP 600ms p75". Action item? Nenhum. Com attribution: "INP 600ms causado por click em .checkout-button, processing 450ms via vendor.js:7234". Action item: investigate vendor.js:7234.
// app/api/rum/route.ts (Next.js Edge runtime)
import { Pool } from 'pg';
const pool = new Pool();
export const runtime = 'edge';
export async function POST(req: Request) {
const text = await req.text();
const m = JSON.parse(text);
await pool.query(
`INSERT INTO rum_events
(event_at, name, value, rating, page, attribution, device, user_id, session_id)
VALUES (now(), $1, $2, $3, $4, $5, $6, $7, $8)`,
[m.name, m.value, m.rating, m.page, m.attribution, m.device, m.userId, m.sessionId]
);
return new Response(null, { status: 204 });
}
Em scale: NÃO escreva direto Postgres. Use Cloudflare Workers Analytics Engine, Snowplow, or queue (Kafka) → ClickHouse pra análise.
export function initRum() {
const sampleRate = 0.1; // 10%
if (Math.random() > sampleRate) return;
onLCP(sendToAnalytics);
// ...
}
journey field:
window.__journey = 'pickup-confirm'; // set on route change
| Tool | Modelo | Strengths | Custo (1M PV/mês) |
|---|---|---|---|
| SpeedCurve LUX | SaaS RUM-only | Best UX dashboards; budget alerts; CrUX comparison | ~$1k/mês |
| Datadog RUM | SaaS APM + RUM | Correlação RUM ↔ backend traces | ~$3k/mês |
| Sentry Performance | SaaS error + perf | Boa pra times já em Sentry | ~$500/mês |
| Cloudflare Web Analytics | SaaS, free | Web Vitals out-of-box; sem attribution profunda | $0 |
| OpenTelemetry + ClickHouse | Self-host | Full ownership; integra com OTel backend | infra cost ~$200/mês |
sendBeacon ou keepalive: requests cancelados em unload, perde 30-50% das métricas.reportAllChanges: true em produção: 10x volume + custo desnecessário.top/left em vez de transform: dispara layout + paint a cada frame; janky em mobile. transform: translate3d(...) fica em compositor (GPU), main thread livre, INP estável.position: sticky em ancestor que muda durante scroll: cada update do containing block força re-layout em cascata; LoAF picos > 200ms em pages longas. Promova sticky pra container estável + use contain: layout pra isolar.# .github/workflows/perf-budget.yml
- name: Lighthouse CI
run: npx lhci autorun --upload.target=temporary-public-storage
- name: Compare RUM trend
run: |
node scripts/check-rum-regression.js \
--baseline=last-7d-p75 \
--current=last-1d-p75 \
--threshold-lcp=200 \
--threshold-inp=50
PR que aumenta p75 LCP > 200ms ou p75 INP > 50ms → block ou label perf-regression.
Cruza com 03-09 §2.14 (INP foundation), 03-09 §2.16 (performance budget), 03-09 §2.17 (Lighthouse vs reality), 03-07 §2.x (OTel browser SDK feeds RUM into observability stack), 03-15 §2.18 (RUM data alimenta SLO de UX).
§2.19 mede; §2.20 otimiza. Stack moderna 2026 pra esmagar INP e acelerar navigation: Speculation Rules API (Baseline Chrome 2024+, Safari/Firefox WIP) declara prefetch/prerender via JSON; scheduler.yield() (Baseline 2024+) quebra long tasks; Long Animation Frames API (LoAF, Baseline 2024+) substitui Long Tasks com attribution por script. INP substituiu FID em 2024-03 como Core Web Vital — threshold p75 ≤ 200ms no CrUX.
Substitui <link rel="prerender"> (deprecated) e <link rel="prefetch"> (limited). Prefetch baixa HTML pra navigation futura (~300ms TTFB savings); prerender renderiza FULL page off-screen (HTML + CSS + JS execute) — click vira instant nav.
<script type="speculationrules">
{
"prerender": [{
"where": {
"and": [
{ "href_matches": "/orders/*" },
{ "not": { "selector_matches": ".no-prerender" } }
]
},
"eagerness": "moderate"
}],
"prefetch": [{
"where": { "href_matches": "/api/*" },
"eagerness": "conservative"
}]
}
</script>
eagerness levels: immediate (start now), eager (high prob), moderate (hover/touchstart trigger ~200ms hold), conservative (mousedown trigger). Trade-off: immediate/eager = mais bandwidth + battery drain; conservative = less savings mas safer em mobile data.fetch, localStorage, analytics) off-screen; pode disparar pageview duplicado. Use document.prerendering flag:if (document.prerendering) {
document.addEventListener('prerenderingchange', () => sendAnalytics(), { once: true });
} else {
sendAnalytics();
}
INP mede WORST interaction latency em entire session (input delay + processing duration + presentation delay). Threshold p75 CrUX: ≤ 200ms good, ≤ 500ms needs improvement, > 500ms poor.
Causes comuns de high INP:
element.offsetHeight após style write força sync layout).ResizeObserver / IntersectionObserver callbacks.Optimizing — copy-paste primitives:
// scheduler.yield() — Baseline 2024+. Pausa e deixa input handlers rodarem.
async function processItems(items) {
for (const item of items) {
await scheduler.yield();
doExpensive(item);
}
}
// scheduler.postTask() — priority hints (Chrome 94+).
scheduler.postTask(criticalWork, { priority: 'user-blocking' }); // 16ms slot
scheduler.postTask(uiUpdate, { priority: 'user-visible' }); // default
scheduler.postTask(prefetchData, { priority: 'background' }); // idle
// React 18+ — useTransition marca update non-urgent; React interrompe pra input.
const [isPending, startTransition] = useTransition();
startTransition(() => setFilteredOrders(expensiveFilter(orders, query)));
Padrões adicionais: pre-compute results durante idle (requestIdleCallback), debounce/throttle handlers de typing (lodash.debounce 100ms), evitar synchronous IPC (workers pra heavy compute).
LoAF substitui Long Tasks API: mede frame total > 50ms + breakdown (script, style, layout, paint, render-blocking) + attribution por script com sourceLocation.
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.duration > 100) {
navigator.sendBeacon('/api/loaf', JSON.stringify({
duration: entry.duration,
blockingDuration: entry.blockingDuration,
renderStart: entry.renderStart,
styleAndLayoutStart: entry.styleAndLayoutStart,
scripts: entry.scripts.map((s) => ({
name: s.name,
src: s.sourceURL,
dur: s.duration,
forcedStyleAndLayoutDuration: s.forcedStyleAndLayoutDuration,
})),
url: location.href,
}));
}
}
});
observer.observe({ type: 'long-animation-frame', buffered: true });
Sem LoAF, INP alto fica invisível — dashboard mostra 600ms p75 sem culpado. Com LoAF, attribution aponta OrderDetail.tsx:124 no handler de markDelivered bloqueando 200ms.
02-03 §2.13): cross-fade declarativo via CSS pseudo-elements; compositor GPU = 60fps em low-end mobile. Pegadinha: interaction-blocking até viewTransition.finished — cap duration < 250ms.lazy(() => import('./Modal')) pra heavy modals; bundle analyzer (@next/bundle-analyzer) em CI bloqueia regressão. ESLint regra no-restricted-imports proíbe import _ from 'lodash' (200KB+) — força import debounce from 'lodash/debounce'. Selective re-export: from 'date-fns/format' não from 'date-fns'.<style> no <head> pra zero-FOUC; font-display: swap (FOUT acceptable) ou optional (no FOUT, fallback if not loaded < 100ms — nunca block em hero text); <link rel="preload" as="font" crossorigin> pra fontes críticas; variable fonts (1 file substitui 8 weights, 30-40KB savings); self-host fonts via Cloudflare/CDN > Google Fonts (privacy + perf)./orders → moderate eagerness prerender /orders/:id → click instant. Numbers reais 2026: 95% navegação interna prerendered hits → INP p75 ~50ms (vs ~200ms cold nav).markDelivered refatorado pra scheduler.yield() chunks; useTransition em search filter de orders.font-display: swap.immediate em todos links — battery + bandwidth drain; use moderate default.prerenderingchange — pageviews duplicados.scheduler.yield() ausente em loops > 100ms — handler bloqueia input, INP > 500ms.import _ from 'lodash' em vez de selective imports (200KB+ deadweight).<link> 3rd party — privacy + render-blocking + extra DNS lookup.font-display: block em hero — FOIT 3s reduz perceived perf.Cruza com 02-04 (React 19, useTransition + concurrent rendering), 02-05 (Next.js, Speculation Rules em App Router), 03-07 (observability, RUM + LoAF integration), 02-03 (Web APIs, View Transitions), 02-19 (i18n, font subsetting per locale).
Imagens dominam payload web (Web Almanac 2024: median 1MB+ por page, 40-60% do total bytes) e LCP (Web.dev "Optimize LCP": ~70% das pages têm image como LCP element). Otimização de imagem 2026 não é mais "use WebP" — é stack: format negotiation com <picture>, fetchpriority hints, preload responsivo, OG generation dinâmica, blurhash placeholders. Decisões erradas (lazy em LCP, preload sem srcset) regridem performance vs ausência de otimização.
Format matrix 2026. AVIF é default escolha — Safari 16+ (GA desde 2022), Chrome/Edge/Firefox GA, ~50% smaller que JPEG mesma quality (Cloudinary 2024 image study). WebP universal fallback (Safari 14+, IE morto), ~30% smaller que JPEG. JPEG XL ainda niche em 2026: Safari 17+ shipped, Chrome behind --enable-features=JpegXl flag, Firefox flag (Can I Use Q1 2026: <60% global support sem flag). Conclusão: pivot para AVIF + WebP fallback, JPEG XL ainda não vale build complexity. <picture> com type ordering deixa browser escolher:
<picture>
<source type="image/avif" srcset="hero.avif" />
<source type="image/webp" srcset="hero.webp" />
<img src="hero.jpg" alt="Courier entregando pacote" width="1200" height="630" />
</picture>
Browser pega o primeiro <source> que entende; <img> é fallback final. width/height evitam CLS (reserva space antes do load).
fetchpriority attribute (Baseline 2024). All browsers 2026, suporta high/low/auto. Hero/LCP image = fetchpriority="high", below-fold thumbnails = low. Cloudinary 2024 reports 25% LCP improvement em e-commerce hero images apenas adicionando fetchpriority="high". Browser default deprioriza imagens fora do initial viewport; fetchpriority="high" força priority lane:
<img
src="hero.avif"
alt="Hero"
fetchpriority="high"
decoding="async"
width="1200"
height="630"
/>
loading="lazy" + decoding="async" combo. loading="lazy" é native (substitui IntersectionObserver code), browser lazy-loads quando próximo do viewport. decoding="async" faz decode off-main-thread, evita blocking de paint em images grandes. Anti-pattern crítico: loading="lazy" em LCP image — browser pula priority hints, LCP regride 1-3s (Web.dev "Optimize LCP" warning explícito). Aplique lazy apenas em non-LCP, abaixo do fold.
LCP image preload. Quando LCP image é conhecida server-side (hero estático, product image route-driven), preload em <head> ganha 100-300ms vs descoberta via parser:
<link
rel="preload"
as="image"
fetchpriority="high"
imagesrcset="hero-800.avif 800w, hero-1200.avif 1200w, hero-1600.avif 1600w"
imagesizes="(max-width: 768px) 100vw, 1200px"
type="image/avif"
/>
imagesrcset + imagesizes fazem responsive preload — browser baixa só a variant correta para o DPR/viewport. Risk: preload errado (wrong format, no srcset) bloqueia outras requests críticas (CSS, JS) e regride LCP.
Responsive images deep. srcset w-descriptors (hero-800.avif 800w) vs x-descriptors (hero@2x.avif 2x) — w-descriptors são mais flexíveis, browser combina com sizes + DPR. sizes query DEVE refletir o layout CSS real ((max-width: 768px) 100vw, 50vw); errado = browser baixa size errado. DPR awareness: 2x density mainstream em mobile 2026, 3x em iPhone Pro/Pixel Pro flagship. Art direction (crop diferente per breakpoint) requer <picture> com media, não srcset. Next.js 15 <Image> automatiza:
import Image from "next/image";
<Image
src="/hero.jpg"
alt="Courier"
width={1200}
height={630}
priority
sizes="(max-width: 768px) 100vw, 1200px"
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>;
priority injeta preload + fetchpriority="high" automático; sizes deve match CSS real.
OG image generation. Vercel @vercel/og (Satori + Resvg, edge runtime) gera imagens dinâmicas per-request com cache headers. Cloudflare Workers og package equivalente para CF stack. Aspect ratio: 1200x630 (Twitter/X, Facebook, default OG), 1200x627 (LinkedIn — 3 pixels diferentes mas tolerante). Fonts em edge: subset Inter/Geist (~30KB woff2) carregado via fetch + cache. Vercel docs @vercel/og:
// app/og/[orderId]/route.tsx (Next.js 15)
import { ImageResponse } from "next/og";
export async function GET(
req: Request,
{ params }: { params: Promise<{ orderId: string }> },
) {
const { orderId } = await params;
const order = await fetchOrder(orderId);
return new ImageResponse(
(
<div style={{ display: "flex", fontSize: 48 }}>
Pedido {orderId} — ETA {order.eta}min
</div>
),
{
width: 1200,
height: 630,
headers: { "cache-control": "public, max-age=3600, s-maxage=86400" },
},
);
}
Blurhash placeholders. Pré-compute hash em build (server-side, biblioteca blurhash Node), inline base64 ~20-30 bytes vs full thumbnail KB. Renderiza placeholder borrado enquanto image real carrega, percepção de speed +200-400ms. Alternativas: thumbhash (Wolt, mais smooth gradients, ~25 bytes), Next.js placeholder="blur" + blurDataURL automático em static imports, plaiceholder lib (gera SVG/CSS/blurhash). Pinterest, Wolt, Unsplash usam blurhash + dominant color em produção.
Image CDN comparison 2026. Cloudflare Images $5/100k images stored + $1/100k delivered (cheap, integrado com Workers/Pages). Cloudinary mais features (AI crop, video, transformations complexas), mais caro ($89+/mo). imgix integrado bem com S3, pricing por volume. ROI: image CDN paga-se em LCP improvement em e-commerce (Web.dev case studies: 1s LCP improvement = +7-10% conversion). Self-hosted alternativa: sharp + Next.js Image Optimization API on-demand (free, mas requer compute + cache).
Image accessibility. alt attribute obrigatório em todas images. Decorative images: alt="" explicit (vazio) é melhor que omit — screen reader pula explicitamente vs anuncia filename. aria-hidden="true" em decorative quando combinado com texto adjacente equivalente. <figure> + <figcaption> para semantic grouping (image + caption ligados programaticamente). Nunca use texto em imagem como único veículo de informação (i18n + a11y break).
Image decode budget + INP-aware loading 2026. Imagem grande não bloqueia só network — bloqueia decode na main thread. decoding="async" mitiga mas Chrome ainda pode promover decode síncrono em layout-critical paints. Strategy 2026:
<img loading="lazy" decoding="async"> para below-fold + content-visibility: auto em containers grandes (CSS skip render off-viewport).fetchpriority + Speculation Rules combo — pré-carrega LCP image enquanto pre-renders próxima rota com Speculation Rules API (cruza com §2.20 Speculation Rules). LCP image preload + speculation prerender = navegação instant.requestIdleCallback para non-critical decoding — Image() pré-carregadas em idle callback (e.g., heroes secundários, próxima foto carousel). Browser decode em idle, no contention com user input.<!-- Hero LCP, decode síncrono priority -->
<img src="/hero.avif" alt="..." fetchpriority="high" decoding="sync"
width="1200" height="630">
<!-- Below fold, decode off-main, layout reserved -->
<div style="content-visibility: auto; contain-intrinsic-size: 600px 400px;">
<img src="/below.avif" alt="..." loading="lazy" decoding="async"
width="1200" height="800">
</div>
// Idle prefetch de próxima imagem em carousel
function prefetchNext(url: string) {
if ('requestIdleCallback' in window) {
requestIdleCallback(() => { const i = new Image(); i.src = url; });
} else { setTimeout(() => { const i = new Image(); i.src = url; }, 200); }
}
Real impact 2026: stack (hero AVIF + content-visibility: auto + Partytown pra third-party scripts) consistentemente reportada em case studies Web.dev como caminho pra INP p75 < 200ms (target "Good"). Shopify Hydrogen Cookbook documenta Partytown recipe pra GTM/Segment offload (hydrogen.shopify.dev/cookbook). Magnitude do ganho varia por baseline — projete 30-50% INP redução sobre baseline com main-thread bloqueada por 3rd-party scripts.
Logística applied. /tracking page tem hero map screenshot 1200x630 servida via Cloudflare Images como AVIF (primary) + WebP (fallback) + JPEG (legacy), fetchpriority="high" + <link rel="preload"> no <head> server-rendered. Map screenshot do trajeto courier→destino tem blurhash placeholder (gerado em build a partir do dominant route color) enquanto tile real carrega. OG image dinâmica em app/og/[orderId]/route.tsx com courier ETA, distância restante e nome da loja — compartilhamento em WhatsApp/Twitter mostra preview rico, +30% click-through em testes A/B.
Cruza com. 02-01 (HTML/CSS, <picture> element + aspect-ratio CSS para reservar space pré-load), 02-05 (Next.js 15 <Image> component + next.config.js remote patterns para Cloudflare Images), 03-09 §2.1 (LCP é image em 70%+ das web pages — image optimization é LCP optimization), 03-09 §2.8 (cobre imagens overview, esta seção é deep-dive 2026), 03-04 (CI/CD pipeline com image build optimization step: Sharp pre-process + blurhash gen + manifest).
Anti-patterns.
loading="lazy" em LCP image — browser deprioriza, LCP regride para 4s+ (Web.dev warning).imagesrcset/imagesizes em responsive image — desktop variant baixada em mobile, waste 2-3x bandwidth.<img> sem width/height — CLS regride, layout salta quando image carrega (CLS > 0.25 = poor).sizes query não match CSS layout real — browser baixa size errado, srcset desperdiçado.fetchpriority="high" em múltiplas imagens — anula priority signal, browser default-prioriza tudo.alt ausente ou genérico ("image", "photo") — fail WCAG 2.2 1.1.1, screen readers anunciam filename inútil.Você precisa, sem consultar:
useMemo, useCallback, memo, useTransition em propósito.Auditar e otimizar front lojista do Logística v1.
/, /login, /dashboard, /orders, /orders/[id]./api/rum).@next/bundle-analyzer. Identifique top 5 contributors.next/image com priority em LCP image.next/font self-hosting.generateStaticParams).lazyOnload.useEffect pra fetch quando server fetch resolve.dangerouslySetInnerHTML sem sanitization (cruzamento com 03-08).prefers-reduced-motion, focus management.Threshold de Maestria
Acerte todas as 5 pra marcar o módulo como concluído. Sem pressa, sem timer. Tudo fica salvo no teu navegador.
Q1Por que lab metrics (Lighthouse) não devem ser a única fonte de decisão sobre performance?
Q2Por que `loading="lazy"` em LCP image é antipadrão crítico?
Q3Quando edge functions (V8 isolates) ganham claramente sobre Lambda Node?
Q4Qual a diferença fundamental entre INP e o antigo FID que ele substituiu em 2024?
Q5Por que `scheduler.yield()` é importante para INP em handlers longos?
Destrava
03-09 é prereq dos seguintes módulos: