Профилировать React локально легко: открыл DevTools, записал взаимодействие, увидел медленные компоненты. В продакшене так не получится. Пользователи не будут запускать Profiler вручную, а проблемы производительности обычно проявляются именно на реальных устройствах, в реальной сети и на реальных данных.

Поэтому полезно уметь собирать легкие метрики рендера прямо в приложении. React для этого дает Profiler и callback onRender. Это не замена DevTools-профилированию, а способ увидеть, какие деревья реально рендерятся долго у живых пользователей.

Ниже разберем, как подключить Profiler осмысленно, какие данные он дает и как отправлять их на бэкенд без лишнего шума.

Что реально измеряет 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 медленный”.