Перейти к основному содержимому

Code Review и автоматизация

Производительность проверяется на этапе Code Review. Ниже — полный сводный чек-лист по всем разделам. Автоматизация через ESLint и мониторинг снижают человеческий фактор и предотвращают регрессии.


Чек-лист для ревьюера

Метрики и измерения

  • Ключевые метрики (LCP, INP, CLS) измеряются в продакшене через RUM
  • Изменения не ухудшают метрики относительно базовой линии

Code Splitting и entry points

  • Код разделён по маршрутам
  • Entry points не содержат бизнес-логики и тяжёлых зависимостей
  • Тяжёлые компоненты загружаются через dynamic() / lazy()
  • Первый экран не блокируется асинхронными чанками
  • 'use client' установлен на минимально возможном уровне — не на уровне страницы или layout
  • RSC / getServerSideProps payload содержит только необходимые поля

Ресурсы

  • Изображения в WebP / AVIF формате, есть fallback
  • srcset / sizes заданы для адаптивных изображений
  • У всех изображений заданы width и height (или aspect-ratio)
  • LCP-изображение имеет fetchpriority="high" и не имеет loading="lazy"
  • Шрифты подключены локально через WOFF2 + font-display: swap
  • Resource hints применяются осознанно и с измеримым эффектом
  • Сторонние скрипты подключены через async/defer или next/script
  • HTTP-кэширование настроено по типу ресурса

Perceived Performance

  • Каждое действие пользователя получает визуальный отклик в течение 100ms
  • Для операций > 100ms есть промежуточный статус (spinner, skeleton)
  • Загрузка не блокирует весь интерфейс целиком
  • Есть skeleton / placeholder вместо пустого экрана
  • Optimistic UI имеет rollback при ошибке
  • Нет последовательных сетевых запросов там, где возможен Promise.allSettled
  • useEffect не используется для синхронизации state
  • В useEffect есть cleanup для таймеров, интервалов, подписок и слушателей событий

Анимации и layout

  • Анимации используют только transform и opacity
  • Нет анимаций top / left / width / height / margin / padding
  • Нет layout thrashing — чтение и запись DOM разделены
  • Соблюдается бюджет кадра ~16.6ms (60 FPS)
  • Long Tasks < 50ms в обработчиках и рендере
  • FLIP используется при неизбежных layout-изменениях

Main thread

  • Нет тяжёлых синхронных операций в обработчиках и рендере (сортировка / фильтрация массивов без ограничения размера, глубокое сравнение структур)
  • Тяжёлые вычисления вынесены в useMemo или Web Worker

Автоматизация — ESLint

MUST

// .eslintrc.js
module.exports = {
extends: [
'next/core-web-vitals', // пресет CWV-правил для Next.js
'plugin:react-perf/recommended', // пресет react-perf
],
rules: {
// Запрет синхронных скриптов
'@next/next/no-sync-scripts': 'error',

// Запрет дублирования полифиллов, уже включённых в Next.js
'@next/next/no-unwanted-polyfillio': 'warn',

// Запрет inline-объектов в props (новый объект на каждый рендер)
'react-perf/jsx-no-new-object-as-prop': 'error',

// Запрет inline-функций в props
'react-perf/jsx-no-new-function-as-prop': 'error',

// Запрет inline-массивов в props
'react-perf/jsx-no-new-array-as-prop': 'error',
},
};
// ❌ Плохо: новый объект/функция/массив на каждый рендер
<Component style={{ color: 'red' }} onClick={() => {}} items={[1, 2, 3]} />;

// ✅ Хорошо: выносим за пределы рендера или мемоизируем
const style = { color: 'red' };
const items = [1, 2, 3];
const handleClick = useCallback(() => {}, []);
<Component style={style} onClick={handleClick} items={items} />;

Автоматизация — мониторинг

Runtime (dev)

ИнструментНазначение
React ScanВизуально подсвечивает компоненты с проблемными рендерами
Why Did You RenderЛогирует причины лишних рендеров в консоль

Production

// Next.js — useReportWebVitals
// CWV + специфичные метрики: hydration, route-change-to-render
import { useReportWebVitals } from 'next/web-vitals';

export function Analytics() {
useReportWebVitals((metric) => {
sendToAnalytics(metric.name, metric.value);
});
}
// web-vitals — для не-Next.js проектов
import { onLCP, onINP, onCLS } from 'web-vitals';

onLCP((m) => sendToAnalytics('LCP', m.value));
onINP((m) => sendToAnalytics('INP', m.value));
onCLS((m) => sendToAnalytics('CLS', m.value));

// performance.mark/measure — замер критичных операций
performance.mark('op-start');
runHeavyOperation();
performance.mark('op-end');
performance.measure('op-duration', 'op-start', 'op-end');

SHOULD

  • Performance budgets для основных страниц в CI.
  • Lighthouse CI для проверки регрессий при деплое.