React Compiler w 2026 — czy useMemo i useCallback są już martwe?

Opublikowano
24 kwietnia 2026
Aktualizacja
31 maja 2026
Czas czytania
9 min czytania

osiągnął stabilną wersję 1.0 w październiku 2025 roku, po okresie testów na produkcyjnych aplikacjach Meta. To moment, w którym dyskusja „czy warto" zamieniła się w „jak wdrożyć". W tym artykule wyjaśniam, na czym polega ta zmiana, dlaczego część dawnych chwytów memoizacyjnych wciąż warto trzymać pod ręką i jak bezpiecznie włączyć Compiler w działającym projekcie.

Krótki skrót praktyczny: w zdecydowanej większości codziennych przypadków useMemo jest dziś zbędne. W nowym projekcie na Next.js po prostu włączasz reactCompiler: true i piszesz czysty kod. W starszym, działającym serwisie idziesz ostrożniej — najpierw linter wyłapuje złe nawyki, potem tryb annotation pozwala na próby na wybranych komponentach, a na końcu globalny infer z codemodem sprzątającym po drodze.

Na czym polega ta zmiana

Compiler działa na etapie buildu. Analizuje Twoje komponenty i automatycznie wstawia memoizację wszędzie tam, gdzie skróci to czas renderowania. Ty piszesz zwykły, czysty kod — bez żadnej dyrektywy w stylu 'use compiled', bez ręcznego oznaczania. Standardowa architektura React zostaje, a narzędzie podczas kompilacji robi resztę.

Najlepiej widać to na przykładzie. Tak wygląda Twój kod:

Code
// Piszesz zwykły, czysty komponent
function Component({ items, onClick }) {
  const sorted = [...items].sort((a, b) => a.price - b.price)
  const handler = () => onClick(sorted[0])
 
  return (
    <Button onClick={handler}>
      Najtańsza opcja: {sorted[0].name}
    </Button>
  )
}

A tak — w uproszczeniu — Compiler przekształca go podczas buildu:

Code
// Compiler generuje z tego (poglądowy zarys mechanizmu):
function Component({ items, onClick }) {
  const cache = useMemoCache()
 
  let sorted
  // Compiler sam sprawdza, co się zmieniło, i przelicza tylko wtedy, gdy trzeba
  if (items !== cache[0]) {
    sorted = [...items].sort((a, b) => a.price - b.price)
    cache[0] = items
    cache[1] = sorted
  } else {
    sorted = cache[1]
  }
 
  let handler
  if (sorted !== cache[2] || onClick !== cache[3]) {
    handler = () => onClick(sorted[0])
    cache[2] = sorted
    cache[3] = onClick
    cache[4] = handler
  } else {
    handler = cache[4]
  }
 
  // ...render
}

Cała praca, którą wcześniej wykonywałeś ręcznie przez useMemo i useCallback, dzieje się teraz automatycznie — przy każdej zależnej wartości, bez dopisywania ani jednej dyrektywy.

Czy useMemo odeszło na emeryturę?

W praktyce: w zdecydowanej większości przypadków już go nie dodajesz. Compiler przejmuje memoizację, którą wcześniej trzeba było pisać z palca:

  • Nie owijasz już przeliczeń w renderze (const total = useMemo(() => items.reduce(...), [items])).
  • Nie stabilizujesz ręcznie funkcji przekazywanych w głąb drzewa (const onClick = useCallback(() => {...}, [])).
  • Nie blokujesz zbędnych renderów komponentów przez React.memo().
  • Nie martwisz się, że przekazanie nowej referencji do komponentu z zewnętrznej biblioteki wymusi jego ponowny render.

W projektach, które przepisywałem na Compiler, usunięcie tej warstwy ręcznej memoizacji nie pogorszyło niczego od strony użytkownika — kod stał się czytelniejszy, a zachowanie pozostało identyczne.

Są jednak sytuacje, w których Compiler potrzebuje pomocy, bo nie ma pełnego wglądu w to, co robisz:

  1. Integracje z systemami spoza Reacta. Gdy podłączasz addEventListener, IntersectionObserver albo zewnętrzne SDK, te wymagają stabilnej referencji funkcji, której Compiler nie zawsze zagwarantuje. Tutaj świadomie sięgasz po useCallback, żeby cleanup działał poprawnie.

  2. Zależności useEffect oparte na zewnętrznych danych. Jeśli efekt zależy od wartości pochodzącej spoza Reacta, sam zadbaj o stabilność referencji w tablicy zależności, żeby nie odpalał się częściej, niż powinien.

  3. Własne hooki ze złożoną logiką. Compiler radzi sobie z większością przypadków, ale przy nietypowych własnych hookach warto zweryfikować zachowanie testami, zanim mu w pełni zaufasz.

  4. Celowe ponowne renderowanie. Jeśli komponent ma się odświeżać świadomie (np. zegar tykający co sekundę), oznacz go dyrektywą 'use no memo' na początku funkcji — Compiler go wtedy pominie.

Gdzie Compiler realnie daje zysk

Postawmy sprawę uczciwie: Compiler nie wskrzesi zapuszczonej aplikacji. Przy małym, lekkim projekcie różnica w wydajności jest kosmetyczna i niewidoczna w pomiarach. Realny zysk pojawia się tam, gdzie jest dużo komponentów, częste aktualizacje stanu i złożone drzewa renderowania.

Z moich wdrożeń, m.in. dla StriveLab oraz aplikacji Army Builder (rozbudowany konfigurator operujący na setkach modeli i tysiącach rekordów):

  • Proste wizytówki na kilkanaście komponentów — różnica praktycznie niemierzalna. Strona działała dobrze i bez Compilera; zysk to przede wszystkim wygoda pisania, nie wydajność.
  • Dashboardy z wyszukiwarką i listami na setki pozycji — tutaj widać już realne odciążenie renderowania, na poziomie porównywalnym z ręczną, staranną memoizacją.
  • Ciężkie konfiguratory z głęboko zagnieżdżonymi strukturami (jak edycja modeli w Army Builderze) — zauważalny spadek czasu blokowania głównego wątku przy interakcjach, przy zerowym nakładzie pracy nad ręczną memoizacją.

Wniosek jest prosty: dla projektów marketingowych Compiler to przede wszystkim komfort i mniej kodu do utrzymania. Dla aplikacji z dużym ruchem i intensywnymi interakcjami — realna oszczędność milisekund, które przekładają się na płynność i konwersję.

Jak wdrożyć Compiler

Compiler instaluje się jako plugin Babela, niezależnie od bundlera (Vite, Next.js):

Code
npm install --save-dev --save-exact babel-plugin-react-compiler@latest

Vite + React

Code
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
 
const ReactCompilerConfig = {
  target: '19', // dostosuj do wersji React w projekcie
}
 
export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [['babel-plugin-react-compiler', ReactCompilerConfig]],
      },
    }),
  ],
})

Next.js

W Next.js 15 funkcja jest jeszcze za flagą eksperymentalną:

Code
// next.config.js (Next.js 15)
module.exports = {
  experimental: {
    reactCompiler: true,
  },
}

W Next.js 16 straciła status eksperymentalny i przeniosła się na główny poziom konfiguracji:

Code
// next.config.js (Next.js 16)
module.exports = {
  reactCompiler: true,
}

W żadnej wersji nie jest włączona domyślnie — musisz to zrobić świadomie, bo Compiler wydłuża czas builda (korzysta z Babela). Zanim ją włączysz, przepuść kod przez linter, żeby wyłapać naruszenia Zasad Reacta.

Bezpieczna migracja starszego projektu

W istniejącej aplikacji nie włączaj Compilera globalnie jednym przełącznikiem. Potraktuj to jak wdrożenie nowej funkcji — etapami.

Krok 1: Zabezpiecz się linterem

Code
npm install --save-dev eslint-plugin-react-hooks@latest
Code
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks'
 
export default [reactHooks.configs.flat.recommended]

Linter wskaże naruszenia Zasad Reacta — mutacje propsów, warunkowe hooki, efekty uboczne w renderze — zanim w ogóle dotkniesz Compilera. To miejsca, które i tak musisz naprawić, bo Compiler je pominie.

Krok 2: Tryb opt-in (annotation)

Na początek włącz Compiler tylko tam, gdzie świadomie go oznaczysz dyrektywą 'use memo':

Code
const ReactCompilerConfig = {
  compilationMode: 'annotation',
}
Code
function Dashboard() {
  'use memo'
 
  // Tylko ten komponent zostanie zoptymalizowany przez Compiler
}

Dzięki temu testujesz nową optymalizację na pojedynczych, dobrze przetestowanych komponentach, zamiast od razu na całym repozytorium.

Krok 3: Tryb globalny (infer)

Gdy najważniejsze komponenty (koszyk, panele użytkownika, checkout) przeszły testy, włącz Compiler globalnie:

Code
const ReactCompilerConfig = {
  compilationMode: 'infer', // tryb domyślny
}

Od tej chwili każdy poprawny komponent jest optymalizowany automatycznie. Komponent, który łamie Zasady Reacta, zostanie pominięty, a w konsoli pojawi się stosowne ostrzeżenie.

Zasady bezpiecznego wdrożenia

  • Przypinaj wersję pluginu Babela na sztywno (--save-exact), nie polegaj na „latest" na produkcji.
  • Najpierw posprzątaj kod linterem, dopiero potem włączaj Compiler.
  • Migruj starsze projekty przez annotation, a globalny infer zostaw na koniec.
  • Przetestuj krytyczne ścieżki E2E — logowanie, płatności, koszyk, formularze — przed wypuszczeniem na produkcję.
  • Nie usuwaj starego useMemo masowo z automatu. Usuwaj stopniowo, sprawdzając pomiary, i uważaj na stabilne referencje potrzebne dla zewnętrznych SDK.

Krok 4: Sprzątanie ręcznej memoizacji

Na końcu, gdy Compiler działa stabilnie, możesz usunąć zbędną ręczną memoizację codemodem:

Code
npx codemod react/19/remove-memoization

W moim wdrożeniu dla Army Buildera codemod usunął bezpiecznie około 350 z 400 ręcznych wywołań useMemo/useCallback, które istniały wyłącznie po to, by ratować render. Pozostałe ~20 — związane z IntersectionObserver i requestIdleCallback — zostawiłem ręcznie, bo to właśnie te przypadki brzegowe, gdzie stabilna referencja jest niezbędna.

Zasady Reacta, na których stoi Compiler

Compiler robi swoje, dopóki Twój kod trzyma się Zasad Reacta (Rules of React). To nie kaprys — to warunek, bez którego automatyczna memoizacja byłaby niebezpieczna. Trzy najczęstsze naruszenia:

1. Nie mutuj stanu ani propsów.

Code
// Źle — mutacja tablicy przekazanej w propsach
function List({ items }) {
  items.sort((a, b) => a.price - b.price); // mutuje oryginał!
  return <ul>{items.map(...)}</ul>;
}
 
// Dobrze — pracuj na kopii
function List({ items }) {
  const sorted = [...items].sort((a, b) => a.price - b.price);
  return <ul>{sorted.map(...)}</ul>;
}

2. Nie wykonuj efektów ubocznych w renderze.

Code
// Źle — efekt uboczny w funkcji renderującej
function Component() {
  document.title = 'Nowy tytuł' // mutacja poza renderem
  return <div />
}
 
// Dobrze — efekt w useEffect
function Component() {
  useEffect(() => {
    document.title = 'Nowy tytuł'
  }, [])
  return <div />
}

3. Nie wywołuj hooków warunkowo.

Code
// Źle — hook pod warunkiem
function Component({ shouldFetch }) {
  if (shouldFetch) {
    const data = useFetch('...')
  }
}
 
// Dobrze — warunek wewnątrz hooka
function Component({ shouldFetch }) {
  const data = useFetch(shouldFetch ? '...' : null)
}

eslint-plugin-react-hooks wykrywa praktycznie wszystkie te naruszenia automatycznie — dlatego linter to pierwszy krok każdej migracji.

Czego Compiler nie naprawi

Łatwo wpaść w pułapkę myślenia, że Compiler to lekarstwo na każdy problem z wydajnością. Nie jest. Optymalizuje render, a nie architekturę — i są klasy problemów, których nie tknie:

  • Złe algorytmy (np. O(n²)). Jeśli komponent wykonuje kosztowną pętlę w pętli, Compiler zapamięta wynik, ale nie poprawi samej złożoności obliczeniowej.
  • Długie listy bez wirtualizacji. Renderowanie 10 tysięcy elementów DOM naraz pozostanie wolne — to zadanie dla wirtualizacji list z TanStack Virtual, nie dla Compilera.
  • Zbyt duży . Compiler nie zmniejszy wagi paczki — od tego jest i analiza bundla.
  • Wodospady zapytań i problem N+1. Kaskadowe, nieefektywne pobieranie danych naprawia się warstwą cache i odpowiednim fetchingiem — np. TanStack Query, nie kompilatorem. Jeśli właśnie skończyłeś porządkować memoizację z pomocą Compilera, naturalnym kolejnym krokiem jest weryfikacja warstwy fetchingu — szczegółowe porównanie TanStack Query, SWR i useEffect opisuję w osobnym artykule.

Compiler przyspiesza — proces dopasowania renderu — a nie leczy źle zaprojektowanej logiki aplikacji. To ważne narzędzie w skrzynce inżyniera, ale nie srebrna kula na złą architekturę.

Gdzie Compiler bywa kłopotliwy

Choć w większości projektów działa bezproblemowo, są sytuacje, w których trzeba uważać:

1. Zapuszczony, stary kod łamiący Zasady Reacta. Tam, gdzie kod opiera się na mutacjach propsów i niestabilnych referencjach, Compiler wycofuje się dla bezpieczeństwa i nie przyspiesza nic. Najpierw posprzątaj kod (linter pokaże gdzie), potem myśl o optymalizacji.

2. Założenia o leniwej ewaluacji. Compiler zmienia moment przeliczania wartości. Jeśli Twój kod opierał się na tym, że ciężkie obliczenie wykona się leniwie dopiero pod warunkiem if, zweryfikuj to zachowanie po włączeniu memoizacji.

3. Starsze wersje TypeScriptu. Typowanie generowane przez Compiler może sprawiać problemy na TS sprzed 5.4. Zaktualizuj TypeScript do 5.4 lub nowszego, a problemy z typami znikają.

4. Integracje przez HOC ze starszymi bibliotekami. Wzorce oparte na HOC (np. starszy Redux czy MobX) bywają trudniejsze dla Compilera. Tam, gdzie to możliwe, przejdź na nowocześniejsze, oparte na hookach API tych bibliotek.

Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czy useMemo i useCallback odeszły w 2026 roku do lamusa?

W większości codziennych scenariuszy — tak. React Compiler automatyzuje memoizację bez żadnych zmian w Twoim kodzie. Ręczne useMemo i useCallback zostawiasz do zadań specjalnych: gdy musisz zachować stabilną referencję dla zewnętrznych narzędzi (event listenery, IntersectionObserver, integracje z SDK), gdy zabezpieczasz zależności useEffect albo gdy obsługujesz naprawdę kosztowne obliczenia. Zespół React rekomenduje, by w nowym kodzie polegać na Compilerze, a ręcznej memoizacji używać tam, gdzie potrzebujesz precyzyjnej kontroli.

Tak, ale z zastrzeżeniem. Compiler oficjalnie wspiera React od wersji 17 w górę, jednak pełnię możliwości wykorzystasz dopiero na React 19+. Dla starszych wersji (17/18) musisz ustawić w konfiguracji parametr target, np. target: '18'. Część nowszych optymalizacji będzie wtedy wyłączona, ale rdzeń działania pozostaje.

Jeśli Twój kod trzyma się Zasad Reacta (Rules of React) — nie. Jeśli je łamałeś (mutowałeś propsy, odpalałeś efekty uboczne w funkcji renderującej, wywoływałeś hooki warunkowo) — Compiler po prostu pominie taki komponent i zostawi ostrzeżenie, zamiast zoptymalizować go błędnie. To celowe zabezpieczenie: narzędzie nie tknie kodu, który mógłby się przez to rozpaść. Linter eslint-plugin-react-hooks wskaże takie naruszenia, zanim w ogóle włączysz Compiler.

Nie ma pośpiechu. Compiler analizuje Twój kod i nakłada własną warstwę memoizacji niezależnie od istniejącej. Zespół React zaleca, by przy migracji zostawić starą memoizację na miejscu, aż upewnisz się, że wszystko działa poprawnie (pomaga React DevTools). Z czasem możesz uruchomić codemod (npx codemod react/19/remove-memoization), ale uważnie — automat potrafi usunąć stabilne referencje potrzebne dla zewnętrznych narzędzi, więc nie traktuj go jako bezpiecznego bez testów.

W większości przypadków tak. React.memo robi płytkie porównanie propsów, żeby uniknąć zbędnego renderu — i dokładnie to Compiler robi teraz automatycznie dla każdego komponentu. Ręcznie sięgniesz po React.memo właściwie tylko wtedy, gdy potrzebujesz własnej funkcji porównującej areEqual, bo Compiler opiera się na standardowym porównaniu Object.is.

Dobrze na obu warstwach. W Next.js z App Routerem Compiler rozpoznaje, czy komponent jest serwerowy, czy kliencki, i dostosowuje optymalizacje. Server Components z natury (brak stanu, brak własnych hooków poza use) wymagają mniej pracy memoizacyjnej niż komponenty klienckie, ale tam, gdzie jest co optymalizować, Compiler to robi.

W Next.js 15 dodajesz do next.config.js wpis experimental: { reactCompiler: true } i instalujesz plugin Babela. W Next.js 16 funkcja straciła status eksperymentalny — ustawiasz reactCompiler: true na głównym poziomie konfiguracji. W obu wersjach nie jest włączona domyślnie, bo wydłuża czas builda, więc musisz ją włączyć świadomie. Przed włączeniem przepuść kod przez linter, by wyłapać naruszenia Zasad Reacta.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów