Zapisz się na szkolenie TERAZ (ostatnie miejsca). Ruszamy za
00 dni
:
00 godz
:
00 min
:
00 sek
Dołącz teraz

React Compiler nie uratuje złej architektury – jak naprawdę optymalizować React?

React Compiler nie uratuje złej architektury – jak naprawdę optymalizować React?

React compiler to nie magia. Dlaczego zła architektura nadal zabija wydajność w React 19 i Next 16.

Włączenie flagi reactCompiler w Next.js 16 nie rozwiązuje automatycznie wszystkich problemów z wydajnością. React 19 wprowadza kompilator automatyzujący proces zapamiętywania, co w teorii eliminuje potrzebę ręcznego stosowania hooków takich jak useMemo czy useCallback. W praktyce narzędzie to optymalizuje wykonanie kodu, ale nie naprawia błędów w architekturze, szczególnie w komponentach klienckich.

Przykładem jest dashboard z tabelą transakcji i wykresami, gdzie najechanie kursorem na wiersz lub wpisanie tekstu w pole wyszukiwania powoduje zablokowanie interfejsu.

Widzisz te fioletowe obramowania? To React Scan pokazuje które komponenty się re-renderują. Przy każdym hover - cała tabela i wykres. Przy każdym keystroke w input - to samo.


Narzędzia do diagnozowania wydajności

Analiza problemów wydajnościowych wymaga odpowiednich narzędzi przed przystąpieniem do modyfikacji kodu. Profiler wbudowany w React DevTools pozwala na pomiar czasu renderowania komponentów i identyfikację przyczyn ich odświeżania, na przykład zmiany konkretnego hooka.

React Scan to narzędzie wizualne, które podświetla na bieżąco odświeżające się elementy interfejsu bezpośrednio w oknie przeglądarki. To właśnie to narzędzie odpowiada za fioletowe obramowania pokazujące które komponenty się re-renderują przy każdej interakcji.

Z kolei React Doctor przeprowadza statyczną analizę kodu w poszukiwaniu antywzorców architektonicznych jeszcze przed uruchomieniem aplikacji.

Nie zapominajmy o narzędziu które ma każda przeglądarka i powinno być naszym pierwszym wyborem, czyli Dev Tools, tutaj możemy uzyskać wiele przydatnych informacji z konsoli czy wielu innych wartościowych zakładek. Przy analizie obecnego problemu posłużyłem się zakładką Performance i nagraniu interakcji, aby zdiagnozować problem z metrykami.

Porównanie wyników WebPageTest - blokujący layout vs streaming

Kliknij w zdjęcie, aby powiększyć

Twarde dane: INP 2662ms

Spójrz na metryki. INP (Interaction to Next Paint) mierzy jak szybko aplikacja reaguje na interakcje użytkownika.

Porównanie wyników WebPageTest - blokujący layout vs streaming

Kliknij w zdjęcie, aby powiększyć

INP: 2662ms - ponad 2.5 sekundy! Google uznaje wszystko powyżej 500ms za “poor”. Mamy 5x więcej.

Co się dzieje? DevTools pokazuje:

  • Scripting: 8,455ms - JavaScript blokuje main thread
  • Przy każdym hover wykres wykonuje 5 milionów operacji
  • I robi to przy każdym ruchu myszy

Dlaczego React “laguje”?

Zastanówmy się najpierw co może sprawić, że React może być miejscami niewydajny i powodować “lagi” UI w naszej aplikacji.

React odświeża komponent w kilku konkretnych sytuacjach:

  • Zmienia się jego state
  • Zmienia się props
  • Re-renderuje się parent
  • Zmienia się Context którego używa

Mechanika Context API powoduje najwięcej problemów w rozbudowanych interfejsach. Zmiana pojedynczej wartości wymusza re-render wszystkich komponentów subskrybujących dany kontekst, niezależnie od tego, z ilu pól faktycznie korzystają. React Compiler nie zapobiega renderowaniu w tym przypadku.


Anatomia problemu

Spójrzmy na kod. Oto jak wygląda “zła” architektura:

// Globalny Context przechowuje stan hovera
const GlobalContext = createContext<{
  hoveredRowId: string | null;
  setHoveredRowId: (id: string | null) => void;
}>();

// Wykres SUBSKRYBUJE Context
const HeavyChart = ({ data }) => {
  const { hoveredRowId } = useGlobalContext(); //  Problem!

  // Ciężka kalkulacja przy KAŻDYM renderze
  for (let i = 0; i < 5000000; i++) {
    // ...miliony operacji
  }

  return <BarChart data={data} />;
};

// Wiersz też subskrybuje Context
const TableRow = ({ transaction }) => {
  const { hoveredRowId, setHoveredRowId } = useGlobalContext();

  return (
    <tr onMouseEnter={() => setHoveredRowId(transaction.id)}>{/* ... */}</tr>
  );
};
Co się dzieje przy hover?

Funkcja setHoveredRowId modyfikuje wartość w globalnym kontekście. Każdy komponent używający useGlobalContext przechodzi proces re-renderowania, włączając w to wykres, co w rezultacie składa się na tak wysokoą wartość metryki INP wynoszącą ponad 2.5 sekundy


Rozwiązanie oparte na architekturze

Kompilator wymaga odpowiednio ustrukturyzowanego kodu do poprawnego działania. Optymalizacja polega na wprowadzeniu zmian w umiejscowieniu stanu i przepływie danych.

Pierwszym krokiem jest przeniesienie stanu najbliżej miejsca jego wykorzystania (State Colocation). Stan najechania kursorem dotyczy wyłącznie pojedynczego wiersza i nie musi być dostępny globalnie.

Zmiana 1: Hover state lokalnie

Czy cała aplikacja musi wiedzieć który wiersz jest hover? Nie. Tylko ten jeden wiersz.

// PRZED: hover w globalnym Context
const TableRow = ({ transaction }) => {
  const { hoveredRowId, setHoveredRowId } = useGlobalContext();
  const isHovered = hoveredRowId === transaction.id;
  // ...
};

// PO: hover lokalnie w wierszu
const TableRow = ({ transaction }) => {
  const [isHovered, setIsHovered] = useState(false);
  // ...
};

Teraz hover zmienia state tylko jednego wiersza. Wykres nie wie i nie obchodzi go że coś się zmieniło.

Zmiana 2: Wykres z niezależnymi danymi

Wykres pokazuje agregaty wszystkich transakcji. Czy musi reagować na filtrowanie tabeli? Nie.

// PRZED: wykres reaguje na każdą zmianę filtra
<HeavyChart data={filteredData} />

// PO: wykres zawsze pokazuje pełny obraz
<HeavyChart data={INITIAL_DATA} />

Teraz wykres nie re-renderuje się gdy wpisujesz w input.

Zmiana 3: React Compiler

Gdy parent się re-renderuje, domyślnie wszystkie dzieci też. Compiler automatycznie memoizuje komponenty - widzi że props wykresu (INITIAL_DATA) się nie zmieniły i pomija re-render.

// next.config.ts
const nextConfig: NextConfig = {
  reactCompiler: true,
};

Porównanie wyników i spadek INP

Po lewej zła architektura, po prawej dobra. Ten sam dashboard, te same dane, ta sama “ciężka” kalkulacja w wykresie. Różnica? Architektura kodu.

Zła architekturaDobra architektura
HoverLag (cała tabela + chart)Płynnie (1 wiersz)
InputLag (chart re-render)Płynnie (tylko tabela)
KoduseGlobalContext() wszędzieLokalny useState + rozdzielone dane

Rezultat: INP 218ms

Po zmianach architektonicznych i włączeniu Compilera:

Porównanie wyników WebPageTest - blokujący layout vs streaming

Kliknij w zdjęcie, aby powiększyć

INP: 218ms - ponad 12x szybciej niż wcześniej!

MetrykaPrzedPoPoprawa
INP2662ms218ms12x
Scripting8,455ms3,700ms2.3x

React Scan (fioletowe obramowania) pokazuje że teraz przy hover re-renderuje się tylko jeden wiersz, a wykres stoi spokojnie.


Co zrobiła każda zmiana?

ZmianaCo naprawiaDlaczego Compiler sam nie wystarczy
Hover lokalnieRe-render 1 wiersza zamiast 250Compiler nie obejdzie Context - gdy Context się zmienia, MUSI re-renderować
Chart z INITIAL_DATAChart nie reaguje na filtrCompiler memoizuje, ale gdy props się zmieniają, musi re-renderować
React CompilerAutomatyczna memoizacjaDziała tylko gdy architektura mu na to pozwala

Kluczowy wniosek: Compiler optymalizuje dobrze napisany kod. Nie naprawia złych decyzji architektonicznych.


Kiedy używać Contextu, a kiedy lepiej nie?

Context sam w sobie nie jest zły, potrafi być bardzo przydatny i często jest nawet konieczny w klasycznych aplikacjach reactowych aby zapewnić dostęp do danych w całej hierarchii komponentów. Warto jednak zapamiętać jedną zasadę: Context może powodować poważne problemy z wydajnością, gdy używasz go do często zmieniającego się stanu.

Context świetnia sprawdzi się dla:

  • Theme (light/dark)
  • Auth (user data)
  • Locale (język)

Context nie będzie dobrym wyborem dla:

  • Hover state
  • Selected items
  • Form inputs
  • Scroll position

Jeśli potrzebujesz globalnego stanu który zmienia się często, rozważ Zustand (selektory) lub Jotai (atomy).


Podsumowanie

  1. Architektura > flagi w konfiguracji - żaden Compiler nie naprawi złych decyzji. State w Context = globalne re-rendery przy każdej zmianie.

  2. State colocation - trzymaj stan jak najbliżej miejsca użycia. Hover wiersza? W wierszu, nie w globalnym Context.

  3. Rozdzielaj niezależne dane - jeśli wykres nie musi reagować na filtr tabeli, nie dawaj mu filtrowanych danych.

  4. React Compiler pomaga - ale przy dobrze napisanym kodzie. Automatyzuje memoizację, ale potrzebuje dobrej architektury żeby mieć co optymalizować.

  5. INP nie kłamie - 2662ms → 218ms. Te dane pokazują jak ważne jest zrozumienie mechaniki i architektury Reacta, a nie poleganie tylko na narzędziach.

Linki

Chcesz wejść głębiej w temat wydajności już teraz? To, co tutaj pokazałem, to fundament nowoczesnego Web Performance. Jeśli chcesz nauczyć się diagnozować wąskie gardła, rozumieć metryki Core Web Vitals i optymalizować aplikacje Reactowe i Next jsowe na poziomie architektonicznym oraz pozostać na czasie z nowościami w świecie Next.js/Reacta – zapraszam Cię na nasze szkolenie:

👉 Sprawdź agendę: Szkolenie WDI - Web Performance City


Autor: Jarosław Gad

Zobacz nasze wideo

Nie zwlekaj. Dołącz do gangu i przejdź przez misje.

To szkolenie to masa wiedzy, praktyki, ale też dobrej zabawy. Wierzymy, że edukacja może być po prostu FAJNA :-)

Dołączam i widzimy się 22-23. kwietnia 2026
WDI Web Performance City - trenerzy Bartek Miś i Jarek Gad