Оптимизация UI-производительности
1) Что считать «быстро»
TTFB (время до первого байта) — быстрый отклик сервера/CDN.
LCP (Largest Contentful Paint) — «главный» контент появился быстро.
INP (Interaction to Next Paint) — отзывчивость при взаимодействии.
CLS (Cumulative Layout Shift) — отсутствие «дрожания» интерфейса.
TTI (Time to Interactive) — когда все уже отвечает.
Рекомендуемые ориентиры: LCP ≤ 2.5 с, INP ≤ 200 мс, CLS ≤ 0.1 (для 75-го перцентиля реальных пользователей).
2) Процесс: измерить → найти узкие места → зафиксировать бюджеты
1. Измерить: RUM (реальные пользователи, перцентили по странам/сетям/девайсам) + синтетика (Lighthouse/обозреватели).
2. Найти: профилировщик Performance (длительные задачи >50 мс, layout thrashing, лишние рендеры).
3. Зафиксировать: бюджеты (вес JS/CSS/шрифтов, LCP/INP) и «красные линии» в CI.
3) Сеть и загрузка ресурсов
3.1 HTTP и приоритеты
Включить HTTP/2/3, сжатие Brotli.
`preconnect` к критичным доменам; `dns-prefetch` для второстепенных.
`preload` для критических ресурсов (герой-изображение, основной шрифт).
`fetchpriority="high/low"` и `priority` подсказки где поддерживается.
3.2 Кэширование
Статика с хешем файла: `Cache-Control: public, max-age=31536000, immutable`.
HTML — короткий TTL + stale-while-revalidate через CDN.
ETag/Last-Modified и Service Worker для офлайн/повторных визитов.
4) Код: меньше, позже, «ровнее»
4.1 Сборка
Tree-shaking, minify (в т.ч. dead-code-elim).
Code-splitting по маршрутам/виджетам, динамический импорт.
Избегать «глобальных» тяжелых пакетов в базовом бандле (moment → Intl/Day.js).
4.2 Рендеринг и доставление HTML
SSR/ISR/стриминг: отдать каркас и главный контент первыми.
Partial/Islands hydration: гидрировать только интерактивные участки.
Defer все некритичное: `<script type="module" defer>`.
4.3 Реакт-специфика (если используете React)
`React.lazy` + `Suspense` для ленивых виджетов.
`startTransition` и `useDeferredValue` для тяжелых фильтров/поиска.
RSC (Server Components) — вычисления на сервере, меньше JS на клиенте.
Селекторы в стейте (zustand/redux): подписывать компонент на фрагменты, а не весь стор.
5) Рендер и состояние: где «тормозит»
5.1 Изоляция ререндеров
Дробите большие компоненты, мемоизируйте (`memo`, `useMemo`, `useCallback`).
Ключи списков — стабильные; не создавайте новые функции/объекты в пропсах без нужды.
Избегайте «глобального» контекста для часто меняющихся данных — используйте селекторы или событийные шины.
5.2 Виртуализация и большие списки
Листы/таблицы → виртуализация (render window).
Пагинация/инфинит-скролл с backpressure (не грузите по 100k строк сразу).
Отложенная инициализация тяжелых виджетов вне вьюпорта.
5.3 Layout & paint
content-visibility: auto; для скрытых секций (браузер не рендерит невидимое).
contain и `contain-intrinsic-size` для предсказуемых размеров.
Избегайте частых чтений-записей layout вперемешку (layout thrashing); группируйте измерения.
will-change используйте дозировано (иначе лишняя память/слои).
6) Изображения и графика
Форматы: AVIF / WebP (fallback на PNG/JPEG).
Responsive-подход: `srcset` + `sizes`, density-based для ретины.
`loading="lazy"` для не-геройских изображений; priority/preload — только для LCP-кандидата.
Плейсхолдеры с фиксированными размерами → нет скачков CLS.
Канвас/чарты: offscreen-канвас и Web Worker для расчетов; батчинг перерисовок.
7) Шрифты и текст
Один-два variable font вместо множества начертаний.
`font-display: swap`/`optional`, preload для основного начертания.
`size-adjust` для уменьшения «скачка» при подмене шрифта.
Локальные fallback-шрифты с похожими метриками.
8) CSS и анимации
Критический CSS инлайн (< 14–20 кБ), остальное — отложить.
Удалить неиспользуемые стили (Purge/CSSTree).
Анимации по возможности на transform/opacity; уважать `prefers-reduced-motion`.
Избегать глубоких каскадов и взрывающих селекторов.
9) Web Workers, поток и тяжелые задачи
Все CPU-тяжелое — в Worker (парсинг, сортировки, агрегации, ML).
Стриминговые API (`ReadableStream`, `fetch` с потоками) для длинных ответов.
Дробление задач на чанки через `requestIdleCallback`/микротаски для сохранения отзывчивости.
10) Стабильность макета (CLS)
Резервируйте место под LCP-элемент (изображение/виджет).
Не вставляйте баннеры/ленты без фиксированных размеров.
Асимметричные тултипы/тосты — не двигать контент; использовать слои/порталы.
11) Примеры сниппетов
Критический шрифт и LCP-изображение
html
<link rel="preload" href="/fonts/Inter. var. woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" as="image" href="/hero. avif" imagesrcset="/hero. avif 1x, /hero@2x. avif 2x" fetchpriority="high">
Ленивая и безопасная инициализация виджета
js const Widget = React. lazy(() => import('./Widget'));
function Section() {
const inView = useInViewport('#sec');
return <div id="sec">{inView? <React. Suspense fallback={null}><Widget/></React. Suspense>: null}</div>;
}
Стабилизация макета
css
.hero {
content-visibility: auto;
contain: layout paint;
contain-intrinsic-size: 720px 320px ;/LCP reserve/
}
12) Контроль регрессий и бюджеты
Bundle-budget: общий JS ≤ N кБ, CSS ≤ M кБ, initial-chunk ≤ K кБ.
Web-Vitals в CI (эмулированные) + RUM-алерты (на перцентилях).
Анализ бандла: source-map-explorer/анализатор в PR.
Перформанс-бенчмарки компонентов (рендер 10k элементов, время реакции).
13) Анти-паттерны
Грузить «все и сразу»: графики, редакторы, карты в первом экране.
Огромный глобальный стейт → каскадные ререндэры.
Изображения в 2–4× от нужного размера, без `srcset/sizes`.
Длинные синхронные циклы на главном потоке.
`outline: none` и кастомные фокусы без оптимизации — мешают индикаторам рендера.
Анимации по `top/left` (ломают компоновку и вызывают reflow).
14) Чек-лист экрана
- LCP ≤ 2.5 c на трафике 3G/мобайл, CLS ≤ 0.1, INP ≤ 200 мс
- Критические ресурсы: preload/приоритеты; остальное — defer/lazy
- Бандлы: code-split, нет лишних зависимостей
- Виртуализация списков/таблиц, отложенная инициализация тяжелых виджетов
- Изображения: AVIF/WebP, `srcset/sizes`, `loading="lazy"`
- Шрифты: variable + `font-display`, preload только нужного
- CSS: критический инлайн, Purge, `content-visibility` и `contain` там, где уместно
- Workers/idle для тяжелых вычислений
- Бюджеты и Web-Vitals подключены к дашбордам/алертам
15) План внедрения (3 итерации)
Итерация 1 — Быстрые победы (1–2 недели)
Включить Brotli/HTTP-2/3, CDN. Критический CSS и preload LCP ресурсов.
Вынести тяжелые виджеты в динамические импорты.
Изображения → AVIF/WebP + `srcset`. Шрифты: `font-display: swap`.
Итерация 2 — Структурные улучшения (3–4 недели)
Code-split по маршрутам, анализ бандла, удаление «тяжелых» либ.
Виртуализация списков, контент-visibility, contain-intrinsic-size.
Внедрить SSR/стриминг/острова (где релевантно).
RUM с Web-Vitals, бюджеты в CI.
Итерация 3 — Масштаб и устойчивость (непрерывно)
Workers/offscreen-канвас, батчинг расчетов, startTransition/deferredValue.
Регулярный перф-аудит, автодайджест регрессий, обучение команды.
16) Мини-FAQ
Что ускорит больше всего на мобайле?
Уменьшение первоначального JS, SSR/стриминг и оптимизация LCP-изображения.
Нужно ли всегда SSR?
Нет. Если страница динамически интерактивна и кэшируется плохо — islands/partial hydration может быть лучше.
Почему INP плохой при «легком» бандле?
Вероятно, долгие задачи (сортировки, графики) на главном потоке — вынесите в Worker и дробите задачи.
Итог
Быстрый UI — это совокупность дисциплин: сетевые приоритеты и кэш, меньшие и поздние бандлы, предсказуемый рендер без скачков, экономные изображения и шрифты, а также постоянный контроль метрик в реальном мире. Введите бюджеты, автоматизируйте проверки и учите команду думать о скорости на каждом шаге — так интерфейс останется быстрым сегодня и через год.