Профилировать React локально легко: открыл DevTools, записал взаимодействие, увидел медленные компоненты. В продакшене так не получится. Пользователи не будут запускать Profiler вручную, а проблемы производительности обычно проявляются именно на реальных устройствах, в реальной сети и на реальных данных.
Поэтому полезно уметь собирать легкие метрики рендера прямо в приложении. React для этого дает Profiler и callback onRender. Это не замена DevTools-профилированию, а способ увидеть, какие деревья реально рендерятся долго у живых пользователей.
Ниже разберем, как подключить Profiler осмысленно, какие данные он дает и как отправлять их на бэкенд без лишнего шума.
- Что реально измеряет React Profiler
- Минимальный пример с onRender
- Как батчить и отправлять метрики
- Где ставить Profiler
- Что важно помнить про profiling build
- Итог
Что реально измеряет React Profiler
Profiler сообщает не “сколько времени загружалась страница”, а сколько времени React потратил на рендер конкретного поддерева компонентов.
Это помогает отвечать на вопросы вроде:
- какой кусок интерфейса слишком часто перерисовывается;
- где memoization реально помогает, а где нет;
- какая часть дерева стала тяжелее после релиза.
У onRender в React 18 шесть аргументов:
function onRender(
id: string,
phase: "mount" | "update" | "nested-update",
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number,
) {
console.log({ id, phase, actualDuration, baseDuration, startTime, commitTime });
}Ключевые поля здесь такие:
actualDuration- сколько реально занял рендер текущего обновления;baseDuration- приблизительная стоимость полного рендера поддерева без оптимизаций;phase- это первыйmountили последующееupdate.
Минимальный пример с onRender
Оберни в Profiler ту часть UI, где хочешь видеть метрики:
import { Profiler } from "react";
function onRender(
id: string,
phase: "mount" | "update" | "nested-update",
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number,
) {
console.log({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
});
}
export function App() {
return (
<>
<Profiler id="Navigation" onRender={onRender}>
<Navigation />
</Profiler>
<Profiler id="MainContent" onRender={onRender}>
<MainContent />
</Profiler>
</>
);
}Идея здесь в идентификаторах. Не называй профайлеры абстрактно вроде Part1 или SectionA. В мониторинге тебе нужны имена, которые сразу говорят, где проблема: CheckoutForm, SearchResults, ProductGallery.
Как батчить и отправлять метрики
Отправлять событие на сервер на каждый render commit - плохая идея. Это и дорого, и шумно. Намного лучше складывать данные в очередь и отправлять пачками.
type ProfileSample = {
id: string;
phase: "mount" | "update" | "nested-update";
actualDuration: number;
baseDuration: number;
startTime: number;
commitTime: number;
};
const queue: ProfileSample[] = [];
export function onRender(
id: string,
phase: "mount" | "update" | "nested-update",
actualDuration: number,
baseDuration: number,
startTime: number,
commitTime: number,
) {
queue.push({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
});
}
export function flushProfiles() {
if (!queue.length) {
return;
}
const payload = JSON.stringify(queue.splice(0, queue.length));
if (navigator.sendBeacon) {
navigator.sendBeacon("/api/react-profile", payload);
return;
}
void fetch("/api/react-profile", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: payload,
keepalive: true,
});
}
window.setInterval(flushProfiles, 5000);На практике обычно полезно добавить еще и sampling:
- включать профилирование не для всех пользователей;
- отправлять только часть событий;
- отбрасывать слишком маленькие обновления;
- группировать данные по release-версии, роуту и типу устройства.
Иначе ты быстро получишь огромный поток данных без понятного сигнала.
Где ставить Profiler
Не оборачивай в Profiler все приложение без разбора. Это редко дает полезную картину.
Лучше начать с мест, где риск выше всего:
- длинные списки;
- автокомплит и поиск;
- формы с тяжелой валидацией;
- экраны, где есть подозрение на лишние re-render;
- ключевые пользовательские сценарии вроде checkout или dashboard.
Если профайлер вложенный, React будет собирать метрики и для внутреннего, и для внешнего поддерева. Это удобно, когда нужно понять, проблема в конкретном компоненте или в более широком контейнере.
Что важно помнить про profiling build
Чтобы получать данные о рендере в продакшене, нужен profiling build React. Это отдельная диагностическая сборка, и ее не стоит включать для всего трафика бездумно.
Практическое правило здесь такое:
- держи profiling build для локальной диагностики, staging или небольшой доли продакшн-сессий;
- не отправляй метрики без отбора на все страницы и для всех пользователей;
- связывай данные Profiler с браузерными метриками, логами ошибок и release-версией.
Сам Profiler полезен только тогда, когда его данные можно сопоставить с реальным сценарием и релизом. Иначе это просто поток чисел.
Итог
React Profiler в продакшене нужен не для тотального наблюдения за всем деревом, а для точечной диагностики дорогих участков интерфейса. Он хорошо работает как легкий слой телеметрии поверх реальных пользовательских сессий, если включать его выборочно и отправлять данные пачками.
Если нужен быстрый старт, оберни один подозрительный экран, собери actualDuration по релизам и посмотри, где именно после изменений выросла стоимость рендера. Даже такой небольшой шаг обычно дает больше пользы, чем абстрактный разговор о “React медленный”.