Оптимізація 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 - це сукупність дисциплін: мережеві пріоритети і кеш, менші і пізні бандли, передбачуваний рендер без стрибків, економні зображення і шрифти, а також постійний контроль метрик в реальному світі. Введіть бюджети, автоматизуйте перевірки і вчіть команду думати про швидкість на кожному кроці - так інтерфейс залишиться швидким сьогодні і через рік.