Сделать React-приложение “прогрессивным” не значит навесить больше технологий. Обычно это значит обратное: сначала убрать лишнюю стоимость интерфейса, а уже потом аккуратно добавить то, что действительно улучшает пользовательский опыт.
Плохая стратегия здесь хорошо знакома: команда сразу думает про SSR, service worker и сложные оптимизации, хотя приложение еще не измерено, рендерит слишком много, грузит слишком большой бандл или ломает базовую доступность. В итоге проблем становится больше, а не меньше.
Ниже разберем набор решений, которые реально двигают React-приложение вперед: измерение, снижение стоимости JavaScript, контроль повторных рендеров, осознанная стратегия рендеринга и аккуратная работа со стилями и доступностью.
- Начинай с измерения
- Сокращай объем JavaScript
- Убирай лишние повторные рендеры
- Выбирай стратегию рендеринга осознанно
- Не ломай доступность и стоимость CSS
- Итог
Начинай с измерения
Оптимизация без измерения обычно заканчивается красивым объяснением и слабым результатом. Для React минимальный практический набор такой:
- панель
Performanceв Chrome DevTools для браузерной картины целиком; - React DevTools Profiler для поиска дорогих деревьев компонентов;
- легкий продакшн-мониторинг через
Profiler, если проблема проявляется только у реальных пользователей.
Минимальный пример Profiler:
import { Profiler } from "react";
function onRender(
id: string,
phase: "mount" | "update" | "nested-update",
actualDuration: number,
baseDuration: number,
) {
console.log({ id, phase, actualDuration, baseDuration });
}
export function App() {
return (
<Profiler id="SearchResults" onRender={onRender}>
<SearchResults />
</Profiler>
);
}Такой код не решает проблему сам по себе, но сразу показывает, какой участок дерева действительно дорогой.
Сокращай объем JavaScript
Самая частая проблема фронтенд-приложения не в том, что React “медленный”, а в том, что браузеру приходится парсить, загружать и выполнять слишком много JavaScript.
Первое, что стоит сделать, - отделить тяжелые экраны и функции в отдельные чанки:
import { lazy, Suspense } from "react";
const CheckoutPage = lazy(() => import("./CheckoutPage"));
function AppRoutes() {
return (
<Suspense fallback={<LoadingPage />}>
<CheckoutPage />
</Suspense>
);
}Практически это значит:
- делай route-level code splitting;
- не тащи тяжелые зависимости в общий initial bundle;
- откладывай редкие сценарии до момента, когда пользователь реально до них дошел.
Если приложение большое, code splitting обычно дает больше пользы, чем преждевременная микрооптимизация отдельных компонентов.
Убирай лишние повторные рендеры
React-приложение редко упирается только в один медленный компонент. Чаще проблема в каскаде лишних обновлений.
Базовые приемы здесь такие:
- мемоизировать действительно тяжелые листья;
- держать состояние ближе к месту использования;
- не передавать каждый раз новые объектные литералы и функции без причины;
- виртуализировать длинные списки.
Например, memo уместен там, где компонент действительно дорогой и часто получает одинаковые props:
import { memo } from "react";
type ProductRowProps = {
product: {
id: string;
title: string;
};
onSelect(id: string): void;
};
export const ProductRow = memo(function ProductRow({
product,
onSelect,
}: ProductRowProps) {
return (
<button onClick={() => onSelect(product.id)}>
{product.title}
</button>
);
});Важно не превращать memo, useMemo и useCallback в религию. Они полезны после измерения, а не вместо него.
Выбирай стратегию рендеринга осознанно
React сам по себе не отвечает на вопрос, как именно страница должна появиться у пользователя. Это архитектурный выбор:
- CSR подходит для внутренних интерфейсов и приложений, где первый экран не критичен для SEO;
- SSG хорошо работает для контентных и маркетинговых страниц;
- SSR нужен там, где важны свежие данные и быстрый первый ответ;
- streaming SSR полезен, когда приложение большое и UI можно отдавать по частям.
Ошибочный ход здесь - выбирать стратегию по моде. Правильный вопрос другой: что для этого экрана важнее всего?
- скорость первого показа;
- SEO;
- свежесть данных;
- цена гидрации;
- сложность инфраструктуры.
Если ответа нет, начни проще. Лишний SSR обходится дороже в поддержке, чем это обычно кажется на старте.
Не ломай доступность и стоимость CSS
Приложение не станет “прогрессивным”, если оно быстро грузится, но плохо читается скринридером, не работает с клавиатуры или мигает без стилей до момента гидрации.
Минимальный набор правил:
- используй семантический HTML до того, как добавляешь
role; - проверь таб-цикл, фокус и видимость интерактивных элементов;
- не делай runtime CSS-in-JS выбором по умолчанию без причины;
- следи за стоимостью шрифтов, изображений и критического CSS так же внимательно, как за JavaScript.
Если у тебя есть выбор, то для обычного продуктового интерфейса сначала стоит рассмотреть CSS Modules или utility-first подход, а runtime CSS-in-JS оставить для случаев, где действительно нужна динамика или библиотечный API.
Итог
Прогрессивный React - это не набор модных технологий, а порядок принятия решений. Сначала измерение, потом уменьшение стоимости JavaScript, затем контроль re-render и только после этого более дорогие архитектурные шаги вроде сложного SSR или агрессивного кэширования.
Если нужен практический ориентир, начни с трех вещей: измерь один медленный экран, отдели тяжелый чанк и убери лишние рендеры в самом дорогом поддереве. Это почти всегда даст больше, чем новая абстракция ради самой абстракции.