Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
// Paso 1: Instala la extensión React DevTools del navegador
// Chrome: chrome://extensions -> busca "React Developer Tools"
// Firefox: Add-ons -> busca "React Developer Tools"
// Paso 2: Abre DevTools -> pestaña Profiler -> Haz clic en Record -> Interactúa -> Detén
// Paso 3: Usa la API React Profiler para medición en producción
import { Profiler, type ProfilerOnRenderCallback } from "react";
const onRender: ProfilerOnRenderCallback = (
id, // "Dashboard"
phase, // "mount" or "update"
actualDuration, // Tiempo dedicado al renderizado (ms)
baseDuration, // Tiempo estimado sin memoización (ms)
startTime,
commitTime
) => {
if (actualDuration > 16) {
// Más largo que un frame a 60fps
console.warn(`Renderizado lento: ${id} tomó ${actualDuration.toFixed(1)}ms`);
}
};
function App() {
return (
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>
);
}Cuándo usarlo: Antes de cualquier trabajo de optimización. Perfila primero, luego arregla. Sin perfilado, podrías optimizar componentes que ya son rápidos mientras pierdes el verdadero cuello de botella.
// ---- Perfilando un dashboard lento y arreglando el cuello de botella ----
// ANTES: Dashboard se renderiza en ~180ms - Profiler muestra que OrderTable es el culpable
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 rendimiento - envía renderizados lentos a tu analítica
const perfLog: ProfilerOnRenderCallback = (id, phase, actualDuration, baseDuration) => {
// Registra todos los renderizados durante el desarrollo
console.table({
component: id,
phase,
actual: `${actualDuration.toFixed(1)}ms`,
base: `${baseDuration.toFixed(1)}ms`,
saved: `${(baseDuration - actualDuration).toFixed(1)}ms`,
});
// En producción, solo reporta renderizados lentos
if (process.env.NODE_ENV === "production" && actualDuration > 50) {
fetch("/api/perf", {
method: "POST",
body: JSON.stringify({ id, phase, actualDuration, baseDuration }),
});
}
};
// Dashboard perfilado - envuelve cada sección independientemente
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)}>Actualizar</button>
</div>
);
}
// ANTES: StatsPanel - recomputa en cada renderizado (~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>Ingresos: ${revenue.toLocaleString()}</div>
<div>Pendiente: {pending}</div>
<div>Enviado: {shipped}</div>
</div>
);
}
// DESPUÉS: StatsPanel - computación memoizada (~0.1ms en re-renderizado)
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>Ingresos: ${stats.revenue.toLocaleString()}</div>
<div>Pendiente: {stats.pending}</div>
<div>Enviado: {stats.shipped}</div>
</div>
);
});
// ANTES: OrderTable - filtra y renderiza 1000 filas en cada renderizado padre (~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>Cliente</th>
<th>Total</th>
<th>
<select value={statusFilter} onChange={(e) => onFilterChange(e.target.value)}>
<option value="all">Todos</option>
<option value="pending">Pendiente</option>
<option value="shipped">Enviado</option>
<option value="delivered">Entregado</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>
);
}
// DESPUÉS: OrderTable - filtrado memoizado y virtualizado (~3ms en re-renderizado)
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]
);
// Muestra solo las primeras 50 filas + virtualiza el resto
const visible = filtered.slice(0, 50);
return (
<table>
<thead>
<tr>
<th>Cliente</th>
<th>Total</th>
<th>
<select value={statusFilter} onChange={(e) => onFilterChange(e.target.value)}>
<option value="all">Todos</option>
<option value="pending">Pendiente</option>
<option value="shipped">Enviado</option>
<option value="delivered">Entregado</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}>
Mostrando {visible.length} de {filtered.length} pedidos
</td>
</tr>
</tfoot>
</table>
);
});Lo que esto demuestra:
Profiler identifica OrderTable como el cuello de botella (160ms vs 12ms para StatsPanel)onRender registra duración real vs duración base, mostrando cuánto ahorra la memoizaciónWhy Did You Render (biblioteca de terceros):
// npm install @welldone-software/why-did-you-render
// wdyr.ts - importa antes que 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,
});
}
// Marca componentes específicos para seguimiento
function ExpensiveList({ items }: { items: Item[] }) {
return <ul>{items.map((item) => <li key={item.id}>{item.name}</li>)}</ul>;
}
ExpensiveList.whyDidYouRender = true;
// Salida de consola:
// ExpensiveList: Se re-renderizó por cambios en props:
// items: [{id: 1}] !== [{id: 1}] (referencia diferente, contenido igual)Marcas de rendimiento personalizadas para producción:
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>;
}
// Ver en Chrome DevTools -> pestaña Performance -> carril TimingsProfilerOnRenderCallback es el tipo para el prop onRender. Importe desde "react".Profiler acepta id: string, onRender: ProfilerOnRenderCallback, y children: ReactNode.phase está escrito como "mount" | "update" | "nested-update".Perfilado en modo desarrollo - React development mode añade sobrecarga significativa (trazas de pila de componentes, efectos invocados dos veces). Los tiempos de renderizado en dev son 2-5x más lentos que en producción. Solución: Usa la compilación react-dom/profiling para perfilado en producción. Añade "profiling": true a la configuración de Next.js.
DevTools ralentizando la app - La extensión React DevTools en sí añade sobrecarga, especialmente con la pestaña Components abierta. Solución: Cierra la pestaña Components mientras perfilas. Solo usa la pestaña Profiler durante la grabación.
API Profiler eliminada en producción - Por defecto, el componente Profiler es una no-op en compilaciones de producción. Solución: Usa la compilación de perfilado: import ReactDOM from "react-dom/profiling" y establece resolve.alias en tu configuración del bundler.
Medir renderizados individuales en lugar de interacciones - Una única grabación de perfil puede no capturar el patrón de interacción real del usuario. Solución: Registra flujos de usuario completos (carga de página, búsqueda, navegación) y mira la línea de tiempo completa.
Ignorar la duración base - La baseDuration en el callback del Profiler te dice cuánto tiempo tomaría el renderizado sin ninguna memoización. Si actualDuration está cerca de baseDuration, la memoización no está ayudando. Solución: Compara ambos valores. Si la diferencia es pequeña, considera eliminar la memoización para reducir complejidad.
| Enfoque | Compensación |
|---|---|
| React DevTools Profiler | Visual, interactivo; solo desarrollo sin compilación de perfilado |
API Profiler | Programático, funciona en producción; requiere compilación de perfilado |
| Why Did You Render | Muestra cambios exactos de props causando re-renderizados; solo dev, añade sobrecarga |
| Pestaña Chrome Performance | Línea de tiempo completa del navegador incluyendo layout y paint; nivel más bajo |
performance.mark / performance.measure | API nativa del navegador; requiere instrumentación manual |
| Lighthouse | Auditoría automatizada; se enfoca en carga de página, no interacciones en runtime |
Perfila primero. Usa React DevTools Profiler para grabar interacciones e identificar qué componentes son realmente lentos. Sin perfilado, podrías optimizar componentes que ya son rápidos mientras pierdes el verdadero cuello de botella.
import { Profiler, type ProfilerOnRenderCallback } from "react";
const onRender: ProfilerOnRenderCallback = (
id, phase, actualDuration, baseDuration
) => {
if (actualDuration > 16) {
console.warn(`Renderizado lento: ${id} tomó ${actualDuration.toFixed(1)}ms`);
}
};
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>Si ambos valores son cercanos, la memoización no está ayudando y puede ser removida para reducir complejidad.
Usa la vista Ranked Chart en el DevTools Profiler. Enumera componentes ordenados por tiempo de renderizado, los más costosos primero. Este es el camino más rápido para encontrar qué componentes optimizar.
Actívalo en la configuración de React DevTools bajo Profiler. Cuando está activado, hacer clic en un componente en el perfil muestra qué props o state específicos cambiaron para desencadenar el renderizado. Esto identifica si un re-renderizado es necesario o desperdiciado.
React development mode añade sobrecarga significativa incluyendo trazas de pila de componentes y efectos invocados dos veces (Strict Mode). Siempre usa la compilación de perfilado (react-dom/profiling) para mediciones precisas en producción.
Sí. La extensión DevTools añade sobrecarga, especialmente con la pestaña Components abierta. Cierra la pestaña Components mientras perfilas y solo usa la pestaña Profiler durante la grabación para obtener resultados más precisos.
El componente Profiler es una no-op en producción por defecto. Para habilitarlo, usa la compilación de perfilado de React DOM y añade "profiling": true a tu configuración de Next.js.
import { type ProfilerOnRenderCallback } from "react";El parámetro phase está escrito como "mount" | "update" | "nested-update".
Registra los cambios exactos de props causando re-renderizados a la consola, incluyendo casos donde props tienen referencias diferentes pero contenido idéntico. Marca componentes con Component.whyDidYouRender = true para seguimiento.
Perfila flujos de usuario completos (carga de página, búsqueda, navegación). Una única grabación de renderizado puede no capturar el patrón de interacción real. Mira la línea de tiempo completa entre múltiples commits para identificar patrones.
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}`
);
}
});Ver resultados en la pestaña Chrome DevTools Performance bajo el carril Timings.
Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥