Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Passo 1: Instale a extensão do navegador React DevTools
// Chrome: chrome://extensions -> procure por "React Developer Tools"
// Firefox: Add-ons -> procure por "React Developer Tools"
// Passo 2: Abra o DevTools -> aba Profiler -> Clique em Gravar -> Interaja -> Pare
// Passo 3: Use a API do React Profiler para medição em produção
import { Profiler, type ProfilerOnRenderCallback } from "react";
const onRender: ProfilerOnRenderCallback = (
id, // "Dashboard"
phase, // "mount" ou "update"
actualDuration, // Tempo gasto renderizando (ms)
baseDuration, // Tempo estimado sem memoização (ms)
startTime,
commitTime
) => {
if (actualDuration > 16) {
// Mais longo que um frame a 60fps
console.warn(`Renderização lenta: ${id} levou ${actualDuration.toFixed(1)}ms`);
}
};
function App() {
return (
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>
);
}Quando usar isso: Antes de qualquer trabalho de otimização. Profile primeiro, depois corrija. Sem profiling, você pode otimizar componentes que já são rápidos, perdendo o gargalo real.
// ---- Profiling de um dashboard lento e correção do gargalo ----
// ANTES: Dashboard renderiza em ~180ms - Profiler mostra OrderTable como o culpado
import { Profiler, useState, useMemo, memo, useCallback } from "react";
import type { ProfilerOnRenderCallback } from "react";
interface Order {
id: string;
customer: string;
total: number;
status: "pending" | "shipped" | "delivered";
date: string;
}
// Logger de desempenho - envia renderizações lentas para sua análise
const perfLog: ProfilerOnRenderCallback = (id, phase, actualDuration, baseDuration) => {
// Registra todas as renderizações durante o desenvolvimento
console.table({
component: id,
phase,
actual: `${actualDuration.toFixed(1)}ms`,
base: `${baseDuration.toFixed(1)}ms`,
saved: `${(baseDuration - actualDuration).toFixed(1)}ms`,
});
// Em produção, reporte apenas renderizações lentas
if (process.env.NODE_ENV === "production" && actualDuration > 50) {
fetch("/api/perf", {
method: "POST",
body: JSON.stringify({ id, phase, actualDuration, baseDuration }),
});
}
};
// Dashboard com profiling - envolve cada seção independentemente
function Dashboard() {
const [orders, setOrders] = useState<Order[]>(generateOrders(1000));
const [statusFilter, setStatusFilter] = useState<string>("all");
const [refreshCount, setRefreshCount] = useState(0);
return (
<div className="grid grid-cols-3 gap-4">
<Profiler id="StatsPanel" onRender={perfLog}>
<StatsPanel orders={orders} />
</Profiler>
<Profiler id="OrderTable" onRender={perfLog}>
<OrderTable
orders={orders}
statusFilter={statusFilter}
onFilterChange={setStatusFilter}
/>
</Profiler>
<Profiler id="ActivityFeed" onRender={perfLog}>
<ActivityFeed refreshCount={refreshCount} />
</Profiler>
<button onClick={() => setRefreshCount((c) => c + 1)}>Refresh</button>
</div>
);
}
// ANTES: StatsPanel - recalcula a cada renderização (~12ms)
function StatsPanel({ orders }: { orders: Order[] }) {
const revenue = orders.reduce((sum, o) => sum + o.total, 0);
const pending = orders.filter((o) => o.status === "pending").length;
const shipped = orders.filter((o) => o.status === "shipped").length;
return (
<div className="grid grid-cols-3 gap-2">
<div>Revenue: ${revenue.toLocaleString()}</div>
<div>Pending: {pending}</div>
<div>Shipped: {shipped}</div>
</div>
);
}
// DEPOIS: StatsPanel - cálculo memoizado (~0.1ms na re-renderização)
const OptimizedStatsPanel = memo(function StatsPanel({ orders }: { orders: Order[] }) {
const stats = useMemo(() => ({
revenue: orders.reduce((sum, o) => sum + o.total, 0),
pending: orders.filter((o) => o.status === "pending").length,
shipped: orders.filter((o) => o.status === "shipped").length,
}), [orders]);
return (
<div className="grid grid-cols-3 gap-2">
<div>Revenue: ${stats.revenue.toLocaleString()}</div>
<div>Pending: {stats.pending}</div>
<div>Shipped: {stats.shipped}</div>
</div>
);
});
// ANTES: OrderTable - filtra + renderiza 1000 linhas a cada renderização pai (~160ms)
function OrderTable({
orders,
statusFilter,
onFilterChange,
}: {
orders: Order[];
statusFilter: string;
onFilterChange: (status: string) => void;
}) {
const filtered = orders.filter(
(o) => statusFilter === "all" || o.status === statusFilter
);
return (
<table>
<thead>
<tr>
<th>Customer</th>
<th>Total</th>
<th>
<select value={statusFilter} onChange={(e) => onFilterChange(e.target.value)}>
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
</select>
</th>
</tr>
</thead>
<tbody>
{filtered.map((order) => (
<tr key={order.id}>
<td>{order.customer}</td>
<td>${order.total}</td>
<td>{order.status}</td>
</tr>
))}
</tbody>
</table>
);
}
// DEPOIS: OrderTable - filtragem memoizada + virtualização (~3ms na re-renderização)
const OptimizedOrderTable = memo(function OrderTable({
orders,
statusFilter,
onFilterChange,
}: {
orders: Order[];
statusFilter: string;
onFilterChange: (status: string) => void;
}) {
const filtered = useMemo(
() => orders.filter((o) => statusFilter === "all" || o.status === statusFilter),
[orders, statusFilter]
);
// Mostra apenas as primeiras 50 linhas + virtualiza o restante
const visible = filtered.slice(0, 50);
return (
<table>
<thead>
<tr>
<th>Customer</th>
<th>Total</th>
<th>
<select value={statusFilter} onChange={(e) => onFilterChange(e.target.value)}>
<option value="all">All</option>
<option value="pending">Pending</option>
<option value="shipped">Shipped</option>
<option value="delivered">Delivered</option>
</select>
</th>
</tr>
</thead>
<tbody>
{visible.map((order) => (
<tr key={order.id}>
<td>{order.customer}</td>
<td>${order.total}</td>
<td>{order.status}</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan={3}>
Showing {visible.length} of {filtered.length} orders
</td>
</tr>
</tfoot>
</table>
);
});O que isso demonstra:
Profiler identifica OrderTable como o gargalo (160ms vs 12ms para StatsPanel)onRender registra a duração real vs base, mostrando quanta economia a memoização proporcionaWhy Did This Render (biblioteca de terceiros):
// npm install @welldone-software/why-did-you-render
// wdyr.ts - importe antes do React
import React from "react";
if (process.env.NODE_ENV === "development") {
const whyDidYouRender = require("@welldone-software/why-did-you-render");
whyDidYouRender(React, {
trackAllPureComponents: true,
logOnDifferentValues: true,
});
}
// Marque componentes específicos para rastreamento
function ExpensiveList({ items }: { items: Item[] }) {
return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
}
ExpensiveList.whyDidYouRender = true;
// Saída do console:
// ExpensiveList: Re-rendered because of props changes:
// items: [{id: 1}] !== [{id: 1}] (referência diferente, mesmo conteúdo)Marcas de desempenho personalizadas para produção:
function useRenderPerfMark(componentName: string) {
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
performance.mark(`${componentName}-render-${renderCount.current}`);
if (renderCount.current > 1) {
performance.measure(
`${componentName}-render`,
`${componentName}-render-${renderCount.current - 1}`,
`${componentName}-render-${renderCount.current}`
);
}
});
}
function Dashboard() {
useRenderPerfMark("Dashboard");
return <div>{/* ... */}</div>;
}
// Veja no Chrome DevTools -> aba Performance -> seção TimingsProfilerOnRenderCallback é o tipo para a prop onRender. Importe de "react".Profiler aceita id: string, onRender: ProfilerOnRenderCallback e children: ReactNode.phase é tipado como "mount" | "update" | "nested-update".Profiling em modo de desenvolvimento - O modo de desenvolvimento do React adiciona sobrecarga significativa (pilhas de chamadas de componentes, efeitos invocados duas vezes). Os tempos de renderização em dev são 2-5x mais lentos que em produção. Correção: Use o build react-dom/profiling para profiling em produção. Adicione "profiling": true à configuração do Next.js.
DevTools retardando o aplicativo - A própria extensão React DevTools adiciona sobrecarga, especialmente com a aba Components aberta. Correção: Feche a aba Components durante o profiling. Use apenas a aba Profiler durante a gravação.
API do Profiler removida em produção - Por padrão, o componente Profiler não faz nada em builds de produção. Correção: Use o build de profiling: import ReactDOM from "react-dom/profiling" e defina resolve.alias em sua configuração de bundler.
Medindo renderizações únicas em vez de interações - Uma única gravação de perfil pode não capturar o padrão real de interação do usuário. Correção: Grave fluxos de usuário inteiros (carregamento de página, pesquisa, navegação) e observe a linha do tempo completa.
Ignorando a duração base - O baseDuration no callback do Profiler informa quanto tempo a renderização levaria sem nenhuma memoização. Se actualDuration estiver perto de baseDuration, a memoização não está ajudando. Correção: Compare ambos os valores. Se a diferença for pequena, considere remover a memoização para reduzir a complexidade.
| Abordagem | Trade-off |
|---|---|
| React DevTools Profiler | Visual, interativo; apenas desenvolvimento sem build de profiling |
API Profiler | Programático, funciona em produção; requer build de profiling |
| Why Did You Render | Mostra as alterações exatas de props que causam re-renderizações; apenas dev, adiciona sobrecarga |
| Aba Performance do Chrome | Linha do tempo completa do navegador, incluindo layout e pintura; nível mais baixo |
performance.mark / performance.measure | API nativa do navegador; requer instrumentação manual |
| Lighthouse | Auditoria automatizada; foca no carregamento da página, não em interações em tempo de execução |
Profile primeiro. Use o React DevTools Profiler para gravar interações e identificar quais componentes são realmente lentos. Sem profiling, você pode otimizar componentes que já são rápidos enquanto perde o gargalo real.
import { Profiler, type ProfilerOnRenderCallback } from "react";
const onRender: ProfilerOnRenderCallback = (
id, phase, actualDuration, baseDuration
) => {
if (actualDuration > 16) {
console.warn(`Renderização lenta: ${id} levou ${actualDuration.toFixed(1)}ms`);
}
};
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>Se ambos os valores forem próximos, a memoização não está ajudando e pode ser removida para reduzir a complexidade.
Use a visualização Ranked Chart no React DevTools Profiler. Ele lista os componentes ordenados por tempo de renderização, do mais caro primeiro. Esta é a maneira mais rápida de encontrar quais componentes otimizar.
Habilite-o nas configurações do React DevTools em Profiler. Quando habilitado, clicar em um componente no perfil mostra quais props ou estado específicos mudaram para acionar a renderização. Isso identifica se uma re-renderização é necessária ou desperdiçada.
O modo de desenvolvimento do React adiciona sobrecarga significativa, incluindo pilhas de chamadas de componentes e efeitos invocados duas vezes (Strict Mode). Sempre use o build de profiling (react-dom/profiling) para medições precisas em produção.
Sim. A extensão DevTools adiciona sobrecarga, especialmente com a aba Components aberta. Feche a aba Components durante o profiling e use apenas a aba Profiler durante a gravação para obter resultados mais precisos.
O componente Profiler não faz nada em produção por padrão. Para habilitá-lo, use o build de profiling do React DOM e adicione "profiling": true à sua configuração do Next.js.
import { type ProfilerOnRenderCallback } from "react";O parâmetro phase é tipado como "mount" | "update" | "nested-update".
Ela registra as alterações exatas de props que causam re-renderizações no console, incluindo casos em que props têm referências diferentes, mas conteúdo idêntico. Marque componentes com Component.whyDidYouRender = true para rastreamento.
Faça profiling de fluxos de usuário inteiros (carregamento de página, pesquisa, navegação). Uma única gravação de renderização pode não capturar o padrão real de interação. Observe a linha do tempo completa em vários commits para identificar padrões.
useEffect(() => {
renderCount.current += 1;
performance.mark(`${name}-render-${renderCount.current}`);
if (renderCount.current > 1) {
performance.measure(`${name}-render`,
`${name}-render-${renderCount.current - 1}`,
`${name}-render-${renderCount.current}`
);
}
});Visualize os resultados na aba Performance do Chrome DevTools, na seção Timings.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥