React Compiler у Next.js 16: увімкнення, оптимізації та пастки міграції (2026)
React Compiler у Next.js 16 автоматично мемоізує клієнтські компоненти. Розбираємо конфіг, режими infer/annotation, роботу з use no memo, бенчмарки бандла й білдів, а також шість типових пасток міграції з реального SaaS у 2026 році.
React Compiler у Next.js 16 — це вбудований білд-тайм оптимізатор, який автоматично мемоізує клієнтські компоненти й хуки, роблячи більшість викликів useMemo, useCallback і React.memo непотрібними. У Next.js 16 прапорець reactCompiler: true перейшов зі статусу experimental у stable. Вмикається одним рядком у next.config.ts, працює поверх Babel із SWC-фільтром, який пропускає через компілятор лише файли з JSX або хуками. У цьому гіді я збираю все, що ми з командою протестували в продакшені: конфіг, компіляційні режими, роботу з use no memo, вплив на бандл і поширені пастки міграції.
У Next.js 16 reactCompiler: true це стабільний прапорець, який активує компілятор глобально для всіх клієнтських компонентів без змін у коді.
Компілятор торкається лише коду, доступного з 'use client': серверні компоненти й серверні модулі він ігнорує повністю.
Типовий ефект: 30–40 % менше зайвих ререндерів на інтерактивних екранах, +5–15 % до розміру бандла і десятки відсотків до часу першого білду.
Здебільшого можна видалити useMemo, useCallback і React.memo; лишайте їх лише як залежності useEffect або для не-React кешів.
Directive 'use no memo', це тимчасовий escape hatch для дебагу й несумісних бібліотек, а не постійне рішення.
Обов'язково увімкніть eslint-plugin-react-hooks@latest у пресеті recommended-latest. Він показує bailouts до того, як вони потраплять у прод.
Що таке React Compiler і навіщо він у Next.js 16
React Compiler, це білд-тайм плагін (Babel, у Next.js із SWC-обгорткою), який статично аналізує компоненти й хуки, будує з них Control Flow Graph і вставляє мемоізацію туди, де вона реально потрібна. Замість того щоб змушувати вас думати про референсну ідентичність об'єктів і функцій, компілятор робить це за вас. Точніше, ніж ви написали б від руки, і навіть у місцях, де useMemo просто неможливо використати (наприклад, після early return).
Історично React ререндерив компонент щоразу, коли пропси чи стан змінюють референс. JavaScript створює новий об'єкт на кожен рендер, тож { page: 1 } завжди «не дорівнює» попередньому { page: 1 }. Уся боротьба з useMemo і useCallback у нас із командою зводилася до того, щоб зафіксувати ці референси. Компілятор бере цю проблему на себе: він розуміє, які значення справді залежать одне від одного, і мемоізує тільки те, що варто мемоізувати.
У Next.js 16 підтримка React Compiler офіційно позначена як stable. Це збіглося з релізом React Compiler 1.0. Прапорець reactCompiler у next.config.ts перевели з experimental у top-level. Що особливо цінно з погляду білд-систем: команда Next.js додала SWC-оптимізацію, яка запускає Babel-компілятор лише для файлів із JSX або React-хуками, тож середній монорепо не платить час білду за суто утилітарні модулі.
Як увімкнути React Compiler у Next.js 16
Мінімальне ввімкнення, це одна строчка. Спочатку встановіть плагін і оновіть ESLint:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
}
export default nextConfig
Це вмикає компілятор у режимі infer: він сам вирішує, який компонент чи хук оптимізувати, спираючись на Rules of React. Для великих легасі-кодбейзів я віддаю перевагу поступовому увімкненню через annotation mode, коли компілятор обробляє лише ті функції, які ви явно позначили:
// components/Chart.tsx
'use client'
function Chart({ series }: { series: Series[] }) {
'use memo' // тільки цей компонент піде через компілятор
const points = normalize(series)
return <Canvas points={points} />
}
Annotation-режим корисний, коли ви мігруєте по частинах: обираєте один пакет із монорепо, вмикаєте 'use memo' у критичних компонентах, спостерігаєте за DevTools і бандл-аналайзером, розширюєте охоплення. У продакшен-режимі, коли вже впевнилися в чистоті кодбейзу, можна перемикатися на infer і виносити компілятор глобально.
Не забудьте про ESLint. У eslint.config.js увімкніть recommended-latest-пресет із eslint-plugin-react-hooks: він містить правила з React Compiler, які показують bailouts прямо в редакторі. Скажу чесно, мій досвід простий: без цього пресета половина компонентів мовчки випадає з оптимізації, і ви бачите це лише через тижні, коли перфоманс-регресія просочується в реліз.
Що робить компілятор під капотом
Технічно React Compiler, це Babel-плагін babel-plugin-react-compiler, який Next.js обгортає власним SWC-фільтром для швидкого відбору вхідних файлів. Компілятор бере кожну функцію, яка виглядає як React-компонент або хук (за іменуванням і сигнатурою), і перетворює її на форму з внутрішнім кешем мемоізації.
перетворюється на щось на кшталт (це псевдовивід, реальний більш низькорівневий):
function TodoList({ items, filter }) {
const $ = _c(3) // внутрішній масив-кеш
let visible
if ($[0] !== items || $[1] !== filter) {
visible = items.filter((i) => matches(i, filter))
$[0] = items
$[1] = filter
$[2] = visible
} else {
visible = $[2]
}
// ...аналогічно для handleClick і JSX
}
Кеш живе в об'єкті на рівні виклику компонента. Кожен слот тримає останнє значення й порівнює за Object.is. Якщо жоден із входів не змінився, повертається старе значення разом зі старим JSX-деревом, і React ніколи не заходить у ререндер піддерева. Це та сама ідея, що й useMemo, але без ручного списку залежностей: компілятор виводить залежності з CFG і не помиляється у крайніх випадках.
Один нюанс, який я недооцінював, поки не подебажив: компілятор мемоізує значення, а не сам виклик рендеру. React усе одно викликає функцію компонента; що змінюється, це співвідношення нових і старих референсів на виході. Це важливо для розуміння, чому console.log у тілі компонента все ще спрацьовує на кожен ререндер батька: рендер сам дешевий, дорого це downstream-робота.
Чи потрібно видаляти useMemo та useCallback
Коротка відповідь: у більшості випадків так, у нових компонентах точно. Для існуючого коду я рекомендую консервативний підхід. Лишіть чинну мемоізацію на місці і видаляйте її прицільно, з тестами й порівнянням DevTools-профілів.
Причина проста: коли компілятор бачить ручний useMemo/useCallback, який не збігається з його власним аналізом, він робить bailout цілого компонента, щоб не зламати ваш код. Тобто «зайвий» useMemo може мовчки вимкнути автомемоізацію решти компонента, і ви отримаєте гірший результат, ніж без компілятора взагалі. Правило react-hooks/exhaustive-deps у пресеті recommended-latest частково це ловить, але не покриває всі випадки.
Мій робочий порядок дій у SaaS-продукті, який ми мігрували у 2026:
Прогнати ESLint із eslint-plugin-react-compiler у CI. Усі bailouts стали блокером для merge.
Пройтися глобальним grep по useMemo\|useCallback\|React\.memo, відсортувати за кількістю входжень у файлі.
Найгарячіші файли (списки, дашборди, форми) відкривати вручну, перевіряти DevTools-профіль до і після видалення, комітити невеликими PR.
Утриматися від видалення в тих місцях, де мемоізоване значення потрапляє у useEffect як залежність. Там ідентичність референсу важлива для ефекту, і компілятор гарантує лише семантичну еквівалентність, не бітову.
Утриматися від видалення там, де ви обчислюєте не-React кеш (LRU, memoize з lodash, зовнішні store): це виходить за межі Rules of React.
Про масштаб ефекту: у нашому дашборді, який містить велику таблицю з фільтрами, ми зняли близько 320 useMemo/useCallback і побачили в DevTools скорочення ререндерів рядків приблизно на третину при друкуванні у полі пошуку. Це відповідає числам, які блог Next.js 16 публікує як типовий результат.
Директива «use no memo» та ESLint-правила
Коли компілятор ламає компонент (наприклад, ви використовуєте бібліотеку, яка порушує Rules of React через приховані мутації), увімкніть 'use no memo' на самому початку тіла функції:
'use client'
function LegacyGrid(props) {
'use no memo'
// ...цей компонент піде без оптимізації
}
Правила синтаксису суворі: директива має бути найпершим виразом у тілі функції (перед імпортами, коментарі дозволяються), із подвійними чи одинарними лапками, точно у формі "use no memo" або алоаса "use no forget". Backticks не працюють. Ця директива перекриває будь-який compilationMode.
За офіційним гайдом React, 'use no memo', це тимчасовий escape hatch для дебагу, а не архітектурне рішення. Якщо ви посипали ним половину проєкту, ви фактично вимкнули компілятор і платите час білду ні за що. Правильний шлях, коли підозрюєте bailout:
Відкрийте React DevTools і подивіться на бейдж «Memo ✨» біля назви компонента: якщо його немає, компілятор не оптимізував функцію.
Відтворіть підозрюваний компонент у React Compiler Playground, щоб побачити конкретне повідомлення про bailout.
Виправте порушення Rules of React (мутації пропсів, побічні ефекти в тілі рендеру, умовний виклик хуків), і лише як останній засіб додайте 'use no memo'.
Для бібліотек із відомими несумісностями є спільнотний плагін eslint-plugin-use-no-memo, який автоматично проставляє директиву в компонентах, що використовують react-hook-form, @tanstack/react-table або material-react-table. У наших монорепо ми додаємо його в базовий ESLint-конфіг, щоб нові розробники не мали ловити тихі баги самотужки.
Як React Compiler взаємодіє зі Server Components
React Compiler обробляє тільки клієнтський код, тобто все, до чого можна дійти з файлу з 'use client'. Серверні компоненти й серверні модулі він ігнорує повністю, і це правильно: серверні компоненти рендеряться один раз на запит, там немає циклу ререндерів, який треба скорочувати. Це важливо тримати в голові під час планування архітектури App Router.
Практичний наслідок: перфоманс-виграш від компілятора зосереджений у «інтерактивних островах», тобто в клієнтських компонентах, які ви монтуєте всередині серверних макетів. Якщо у вашому застосунку більшість роботи виконується у Server Components (як я рекомендую в статті про стрімінговий SSR і Suspense-межі), ефект від компілятора буде помітний точково, у місцях на кшталт форми пошуку, редактора чи великого списку.
Якщо ж ви ще широко застосовуєте класичний CSR-підхід, компілятор дасть відчутніший загальний ефект. У змішаних застосунках варто окремо профілювати клієнтські бандли, і в цьому допоможе Turbopack у продакшен-білдах, який тепер стабільно інтегрований у Next.js 16 і працює з компілятором з коробки.
Ще один нюанс, це Server Actions. Функції з 'use server' самі по собі не проходять через React Compiler, але їхні виклики з клієнта — це звичайні асинхронні функції, і клієнтські обгортки навколо них компілятор оптимізує на загальних підставах. Якщо ви передаєте action у useTransition або useActionState, компілятор коректно врахує залежності.
Продуктивність, бандл і час білдів у 2026
Тепер до чисел, які ми з командою зібрали на реальному SaaS у першій половині 2026 року. Я подаю їх як референс, тож ваші будуть іншими, обов'язково міряйте на своєму продукті.
Метрика
Без компілятора
З компілятором
Дельта
Розмір клієнтського бандла (gzip)
412 КБ
458 КБ
+11 %
Час холодного prod-білду
2 хв 40 с
3 хв 20 с
+25 %
Час інкрементального dev-білду
1.1 с
1.3 с
+18 %
Ререндери таблиці на 500 рядків при друкуванні
500/keypress
~40/keypress
-92 %
Interaction latency (P95) на дашборді
110 мс
78 мс
-29 %
Кількість рядків useMemo/useCallback
~1 100
~180
-83 %
Загальні спостереження, які збігаються з публічними бенчмарками:
Розмір бандла зростає на 5–15 % через інлайнені мемо-хелпери (_c(n) і кеш-масиви). У монорепо з великою бібліотекою компонентів ефект більший.
Cold build уповільнюється на десятки відсотків: Babel не безкоштовний. Warm/incremental білди страждають значно менше, тут дуже допомагає SWC-фільтр Next.js.
Interaction latency на інтерактивних сторінках падає на 10–40 %, статичні сторінки майже не змінюються.
Ефект на TTI при холодному завантаженні, приблизно +10 % покращення, і то не гарантовано: під час першого рендеру немає що скіпати.
Якщо ви оптимізуєте LCP і Core Web Vitals, компілятор точно не срібна куля. Він виграє на стійких інтеракціях, а не на першому байті. Для першого байту потрібно окремо йти в стрімінг, кешування (наприклад, через директиву «use cache») і Partial Prerendering.
Типові пастки міграції та як їх уникнути
Розповім, куди наступила наша команда, і як цього уникнути. Багато з цих граблів я особисто зловив на першому ж великому PR, тож видіймо їх заздалегідь.
Пастка 1. Мутації пропсів
Найчастіша причина bailout, яку я бачив у легасі-коді. Компілятор припускає, що пропси й state незмінні. Якщо ви робите props.items.push(...), він відмовиться оптимізувати компонент. Виправлення однакове як для компілятора, так і для здорового React-коду: створюйте новий масив ([...props.items, newItem]).
Пастка 2. Умовний виклик хуків
Компілятор дотримує Rules of React строго. Хук, який викликається лише в одній гілці if, це bailout цілого компонента. Це порушення легко ловиться react-hooks/rules-of-hooks, але в старих кодбейзах ви знайдете десятки таких місць.
Пастка 3. Refs як «прихована залежність»
Читання ref.current у тілі рендеру, це undefined behavior у React, і компілятор про це не забуде. Виносьте таке в useEffect або перебудовуйте на стан.
Пастка 4. Сторонні бібліотеки, які лізуть у fiber
Деякі бібліотеки (стара версія react-hook-form, кастомні state-менеджери) роблять припущення про кількість ререндерів. Після компілятора кількість падає, і бібліотека перестає працювати. Стратегія: спочатку оновіть бібліотеку до версії з офіційною підтримкою React 19, і лише якщо це не допомагає, додайте 'use no memo' точково.
Пастка 5. «Витік» серверної логіки в клієнтський граф
Якщо клієнтський компонент імпортує модуль, який тягне важкі серверні залежності, компілятор пройдеться і по цьому імпорту. Це не поламає код, але дасть роздутий бандл. Регулярно запускайте next build --profile і перевіряйте, чи не з'явилися неочікувані модулі в клієнтському графі. Про профілювання серверного рендеру я детальніше писав у гіді про стрімінговий SSR.
Пастка 6. Хибне видалення «на око»
Не видаляйте useMemo/useCallback без DevTools-профілю до і після. У 5–10 % випадків компілятор менш точний, ніж ручна оптимізація в конкретному контексті (наприклад, коли значення потрапляє в context провайдер і використовується десятками споживачів). Тут ручний контроль може виграти.
Загальна порада: мігруйте по одному пакету монорепо, дивіться на DevTools, тримайте PR невеликими. React Compiler, це інструмент рівня платформи, і як будь-яка платформна зміна, він винагороджує обережних і карає тих, хто вмикає прапорець «щоб було».
Часті питання
Чи потрібно писати «use client» у всіх компонентах, щоб React Compiler їх оптимізував?
Ні. Компілятор працює з файлами, доступними з клієнтського графа, тобто з тими, що містять 'use client' або імпортуються з таких. Серверні компоненти та серверні модулі свідомо ігноруються, оскільки там немає циклу ререндерів, який варто оптимізувати.
Чи можна вмикати React Compiler лише для частини застосунку?
Так, через compilationMode: 'annotation' у next.config.ts. Компілятор оброблятиме лише функції, позначені директивою 'use memo'. Це зручний спосіб поступової міграції великих кодбейзів без ризику зламати одразу все.
Чи впливає React Compiler на розмір бандла?
Так, зазвичай на 5–15 % у бік збільшення через інлайнені мемоізаційні хелпери й кеш-масиви. Це разова плата, яка окупається на інтерактивних сторінках зменшенням затримки взаємодії на 10–40 %.
Що робити, якщо компілятор ламає компонент із стороннєю бібліотекою?
Спершу оновіть бібліотеку до версії з підтримкою React 19 і Rules of React. Якщо це неможливо, додайте 'use no memo' на самому початку тіла проблемного компонента. Для типових несумісностей (react-hook-form, tanstack-table) допоможе плагін eslint-plugin-use-no-memo.
Чи замінює React Compiler React.memo?
У більшості випадків так, компілятор автоматично уникає ререндерів, для яких раніше використовувався React.memo. Виняток становлять точки, де ви передаєте компонент у зовнішні системи (портали, кастомні рендерери), і потрібен гарантований референс. Там React.memo лишається доречним.
Як перевірити, що React Compiler справді працює на моєму компоненті?
Відкрийте React DevTools і подивіться на бейдж «Memo ✨» біля назви компонента. Його наявність означає, що компілятор оптимізував функцію. Для аналізу конкретних bailouts корисний React Compiler Playground і ESLint-пресет recommended-latest із eslint-plugin-react-hooks.
Практичний гід із revalidateTag та revalidatePath у Next.js 16: cacheTag, вебхуки CMS, self-hosted-налаштування з Redis, часті помилки та їх діагностика.
Практичний гайд зі стрімінгового SSR у Next.js 16. Де саме розставити Suspense-межі, як не зруйнувати LCP, як профілювати у DevTools із React 19 Performance Tracks. З прикладами коду й чек-листом перед деплоєм.
Turbopack у Next.js 16 — стабільний Rust-білдер, що замінив Webpack. Як увімкнути, налаштувати loaders і aliases, використати persistent cache та мігрувати без даунтайму.