Podstawy SSR w React 19.2: Activity, cacheSignal i PPR wyjaśnione
Dowiedz się, w jaki sposób nowy komponent Activity, funkcja cacheSignal oraz technika Partial Pre-rendering w React 19.2 umożliwiają programistom bezpośrednią kontrolę nad wydajnością renderowania na serwerze.
Prace nad wydajnością w Reactie dzielą się na dwie kategorie: przyspieszenie i uproszczenie początkowego renderowania na serwerze oraz zapobieganie marnotrawstwu zasobów podczas renderowania po zamontowaniu aplikacji po stronie klienta. Obie części tego artykułu podchodzą do problemu z przeciwnych stron tego spektrum, ale dzielą tę samą podstawową filozofię — szybkość pochodzi z precyzyjnego określenia dla Reacta, jakie operacje są ważne, które można odłożyć, a które w ogóle nie powinny być wykonywane, zamiast polegania na większym buforowaniu lub lepszym sprzęcie. Pierwsza część omawia renderowanie na serwerze oraz nowe elementy dostępne w React 19.2; druga skupia się na codziennych technikach stosowanych po stronie klienta, które zapewniają responsywność zamontowanej aplikacji.
Ponowne przemyślenie wydajności renderowania na serwerze w React 19.2
Większość wskazówek dotyczących „wydajności SSR” sprowadza się do dodawania kolejnych warstw cache’owania i liczenia na najlepszy wynik. React 19.2, wydany w październiku 2025 roku, oferuje natomiast dedykowane elementy do bezpośredniego kontrolowania pracy serwera. Traktowanie tej wersji jako drobnej poprawki oznacza przegapienie rzeczywistych wzrostów szybkości – to poniższe szczegóły faktycznie przynoszą pozytywne efekty.
Zachowuj komponenty aktywne zamiast je niszczyć
Częstym problemem w aplikacjach SSR jest to, że zmiana kart, otwieranie modali oraz przełączanie tras niszczy komponenty całkowicie, usuwając ich stan i zmuszając do ponownego pobierania danych. Nowy komponent Activity został stworzony specjalnie, aby rozwiązać ten problem.
// old way — full unmount, state gone, effects re-run
{activeTab === 'analytics' && <AnalyticsPanel />}
// React 19.2 — stays alive, just deprioritized
<Activity mode={activeTab === 'analytics' ? 'visible' : 'hidden'}>
<AnalyticsPanel />
</Activity>
Poprzez utrzymywanie zamontowanego ukrytego kontentu zamiast jego niszczenia, React może wcześniej załadować to, co znajduje się w ukrytym bloku Activity, zanim użytkownik na niego kliknie. Takie podejście skutkuje znacznym zmniejszeniem odczuwalnej latencji nawigacji na panelach sterowania z intensywnym SSR, ponieważ nie ma konieczności ponownego pobierania danych ani zmiany układu, gdy treść staje się widoczna.
Pozwól cacheSignal automatycznie usunąć nieukończone operacje
Przed wersją 19.2, jeśli renderowanie na serwerze zostało przerwane w trakcie — na przykład użytkownik przeszedł do innej strony lub żądanie wygasło — wszystkie trwające pobierania danych oraz zapisane dane nie miały sposobu, by dowiedzieć się, że te operacje są już niepotrzebne. cacheSignal rozwiązuje ten problem, dostarczając komponentom React Server Components rzeczywisty sygnał cyklu życia, który można wykorzystać do ich oczyszczenia.
async function getUserOrders(userId, { signal }) {
const res = await fetch(`/api/orders/${userId}`, { signal });
return res.json();
}
Gdy wygaśnie czas trwania pamięci cache, powiązany sygnał uruchamia procedurę przerwania, co zapobiega dalszemu zużyciu zasobów CPU przez nieobsłużone żądania podczas wzrostu ruchu na serwerze.
Zbuduj statyczną strukturę i przesyłaj resztę w formie strumienia
Częściowe uprzednie renderowanie (PPR) to główna funkcja SSR w tym wydaniu. Chodzi o to, aby raz zbudować statyczną strukturę strony – nawigację, układ, stopkę – a następnie dostarczyć ją bezpośrednio z CDN typu edge, przesyłając dynamiczne elementy w formie strumienia poza granicami Suspense.
<Suspense fallback={<ProductSkeleton />}>
<PartialPreRender>
<PersonalizedRecommendations userId={user.id} />
</PartialPreRender>
</Suspense>
Grupuj kilka ujawnień Suspense razem
Wcześniej, gdy kilka granic Suspense rozwiązywało się mniej więcej w tym samym momencie, interfejs użytkownika mógł wykazywać efekt „popcorn”, przy którym fragmenty treści pojawiały się jeden po drugim, zamiast jednocześnie. React 19.2 grupuje te pokazywanie treści, dzięki czemu zachowanie klienta i serwera pozostaje spójne, a także dodaje wsparcie dla Web Streams w Node.js dla zespołów potrzebujących bardziej precyzyjnej kontroli nad transmisją danych.
Te API podnoszą górny pułap, a nie dolny
Nic z tego nie zastępuje podstaw. Nadal musisz eliminować wzorce pobierania danych typu N+1 oraz dzielić monolityczne pliki, zanim te funkcje w ogóle będą mogły ci pomóc. React 19.2 nie obniża minimalnej ilości pracy potrzebnej do optymalizacji — podnosi natomiast górny limit szybkości, jaką może osiągnąć dobrze zoptymalizowana aplikacja. Warto również przeczytać oficjalne notatki dotyczące wersji React 19.2, a także domyślne ustawienia Turbopack wprowadzone w Next.js 16, które dobrze współpracują z PPR.
Od 2026 roku wydajność SSR nie polega już na bardziej agresywnym cacheowaniu — chodzi o to, by poinstruować React, co może czekać, co może być przesyłane strumieniowo oraz co może spowodować awarię w sposób uprzejmy. React 19.2 to wreszcie narzędzie, które daje ci odpowiednie środki do wyrażenia tych koncepcji.
Poza tymi mechanizmami specyficznymi dla SSR, wiele z tego, co sprawia, że aplikacja React wydaje się szybka, wynika z codziennych nawyków, które są ważne niezależnie od strategii renderowania czy wersji React. Podczas gdy poprzednia sekcja koncentrowała się na strumieniowaniu, wstępnym renderowaniu i Suspense na poziomie serwera, to, co następuje, omawia wzorce po stronie klienta, które w praktyce zapewniają responsywność każdej aplikacji React.
Praktyczne techniki poprawy wydajności React w codziennej pracy
React już domyślnie renderuje efektywnie. To w miarę rozwoju aplikacji zaczynają się gromadzić niepotrzebne ponowne renderowania, zbyt duże listy, nadmiar JavaScriptu oraz zbyt wiele żądań sieciowych. Rozwiązanie rzadko wymaga egzotycznych trików – kilka zdyscyplinowanych nawyków zazwyczaj wystarcza, by osiągnąć pożądany efekt.
Renderuj tylko to, co faktycznie wymaga aktualizacji
Każde ponowne renderowanie powoduje, że React uruchamia ponownie ciało funkcji komponentu. To samo w sobie nie jest problemem — prawdziwym kosztem jest powtarzanie drogiego procesu, gdy w rzeczywistości nic istotnego się nie zmieniło. Unikaj umieszczania niespowiązanego stanu wewnątrz komponentu, który renderuje dużą część interfejsu użytkownika, ponieważ aktualizacja tego stanu zmusza do ponownego renderowania całej poddrzewa wraz z nim. Zamiast tego dziel komponenty, aby aktualizacja dotyczyła tylko tej części interfejsu, na którą rzeczywiście ma wpływ. Celem nie jest zerowanie liczby renderowań — chodzi o eliminację tych marnowanych.
Umieść stan tam, gdzie jest naprawdę potrzebny
Ogranicz się i nie przenosź każdego elementu stanu na szczyt drzewa komponentów. Jeśli tylko jeden komponent odczytuje dany wartość, to właśnie tam powinien się on znajdować.
function SearchBox() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}
Zachowywanie stanu lokalnie w ten sposób ogranicza zasięg wpływu aktualizacji, co zmniejsza przypadkowe ponowne renderowanie w innych częściach drzewa. Jako zasada ogólna, umieszczaj stan w pobliżu komponentu, który go wykorzystuje, a nie wyżej w hierarchii.
Wyprowadzaj wartości zamiast je przechowywać
Nie wszystko powinno znajdować się w useState. Jeśli już śledzisz firstName i lastName, nie ma powodu, by osobno przechowywać także fullName. Marnotrawna wersja wygląda tak:
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Prostszym podejściem jest obliczenie wartości podczas renderowania:
const fullName = `${firstName} ${lastName}`;
To eliminuje zarówno dodatkową zmienną stanu, jak i niepotrzebny Effect. Ogólnie rzecz biorąc, jeśli wartość może być obliczona na bieżąco podczas renderowania, prawdopodobnie nie musi być stanem.
Zarządzaj dużymi listami bez przeciążania DOM
Przetwarzanie tysięcy węzłów jednocześnie szybko staje się kosztowne. Wyobraź sobie ekran czatu zawierający 10 000 wiadomości — nie ma potrzeby, aby wszystkie one istniały jednocześnie w DOM-ie. W przypadku dużych zbiorów skorzystaj z jednej z następujących metod:
- Wirtualizacja
- Paginacja
- Rolowanie nieskończone
Wirtualizacja przechowuje tylko te wiersze, które są aktualnie widoczne, plus niewielki bufor, montowany w danym momencie; biblioteki takie jak react-window realizują ten model za Ciebie. Mimo to nie stosuj wirtualizacji bezrefleksyjnie — lista 50 elementów prawie na pewno jej nie potrzebuje.
Odkładaj ładowanie kodu do momentu, gdy będzie potrzebny
Użytkownicy nie powinni musieć pobierać JavaScriptu dla funkcji, których jeszcze nie otworzyli. API lazy i Suspense w React pozwalają oddzielić ten kod od początkowego pliku bundle:
const Settings = lazy(() => import("./Settings"));
Dzięki temu komponent Ustawień jest ładowany tylko w momencie jego rzeczywistego wyświetlenia, a nie przy pierwszym załadowaniu strony. Jest to korzystne dla złożonych lub rzadko używanych funkcji — wykresów, edytorów, map, ekranów ustawień, dużych paneli kontrolnych — i zazwyczaj przyspiesza pierwsze załadowanie.
Zapewnij responsywność interaktywnej interfejsu pod obciążeniem
Nie każda aktualizacja musi odbywać się synchronicznie z działaniami użytkownika. Pole wyszukiwania powinno reagować natychmiast na naciskanie klawiszy, nawet jeśli filtrowanie dużego zbioru danych odbywa się w tle z niższym priorytetem. Hooki Reacta useTransition i useDeferredValue są stworzone właśnie do takiego rodzaju kompromisu. Odbijanie surowych danych wejściowych daje podobny efekt:
const debouncedSearch = useDebounce(search, 500);
Zamiast wysyłać żądanie przy każdym naciśnięciu klawisza, należy czekać, aż użytkownik zrobi przerwę. Takie podejście jest przydatne w polach wyszukiwania, filtrach, długich listach oraz panelach sterowania składających się z wielu elementów w ruchu.
Zmniejsz liczba zbędnych żądań sieciowych
Szybkość renderowania to tylko jeden aspekt – zbyt wiele otwartych żądań może sprawić, że aplikacja będzie wydawać się wolna, nawet jeśli sam proces renderowania przebiega szybko. W zależności od sytuacji warto rozważyć:
- Zachowywanie w pamięci odpowiedzi
- Unikanie duplikowania żądań
- Paginowanie wyników
- Ograniczenie szybkości wprowadzania tekstu w polu wyszukiwania
- Anulowanie żądań, które nie są już istotne
Jeśli użytkownik szybko pisze
react
react performance
react performance optimization
prawdopodobnie nie chcesz, aby trzy oddzielne żądania działały jednocześnie. Podstawowa zasada pozostaje prosta: unikaj zmuszania sieci do wykonywania czynności, których faktycznie nie potrzebujesz.
Wykorzystuj memoizację selektywnie
React dostarcza trzy powszechne narzędzia do memoizacji:
- useMemo przechowuje w pamięci wynik obliczeń.
- useCallback przechowuje referencję funkcji między różnymi renderingami.
- React.memo pozwala pominąć ponowne renderowanie komponentu, jeśli jego props się nie zmieniły.
Typowy przykład:
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.includes(search)
);
}, [users, search]);
Jednak memoizacja nie jest bezkosztowa — wiąże się z dodatkowym obciążeniem i zwiększa złożoność otaczającego kodu. Należy jej używać wtedy, gdy obliczenia są naprawdę kosztowne, gdy komponent jest renderowany bez powodu lub gdy stabilna referencja ma znaczenie w dalszej części struktury. Nie tracić czasu na optymalizację kodu, który nie powoduje wyraźnego problemu.
Pozwól kompilatorowi przejąć część pracy
Nowszym dodatkiem do zestawu narzędzi React jest React Compiler, który może automatycznie zastosować wiele z tych optymalizacji za Ciebie — zapamiętywać wartości, funkcje i komponenty bez konieczności ręcznego pisania tego w każdym przypadku. Oznacza to, że nie musisz już automatycznie korzystać z:
useMemo(...)
useCallback(...)
React.memo(...)
Mimo to React Compiler nie eliminuje konieczności najpierw sprawdzenia, czy rzeczywiście występuje problem wydajnościowy. Właściwą sekwencją działań pozostaje potwierdzenie istnienia rzeczywistego problemu, pozwolenie kompilatorowi na zastosowanie dostępnych optymalizacji oraz ucieczka do ręcznego zapamiętywania tylko wtedy, gdy masz konkretny powód.
Daj React stabilne identyfikatory do pracy
Klucze informują React, który element na liście jest którym podczas kolejnych renderów. Lepiej wywodzić klucz z stabilnej, unikalnej właściwości danych niż z ich pozycji:
items.map(item => (
<Item key={item.id} />
));
Nie należy używać indeksu tablicy jako klucza, ponieważ przestawianie, dodawanie lub usuwanie elementów zmienia wszystkie indeksy poniżej punktu zmiany:
items.map((item, index) => (
<Item key={index} />
));
Stabilny klucz pozwala Reactowi poprawnie zidentyfikować, które elementy zostały dodane, usunięte lub zaktualizowane, zamiast zgadywać na podstawie pozycji. Ten sam princip odnosi się do obiektów i funkcji przekazywanych jako props: tworzenie zupełnie nowego obiektu lub funkcji zwrotnej przy każdym renderowaniu unieważnia cel komponentu potomnego zaimprowizowanego, ponieważ jego props będą się różnić za każdym razem, mimo że nic istotnego się nie zmieniło.
Znajdź rzeczywisty wąskie gardło przed podjęciem działań
Gdy już opanujesz te techniki, nie polegaj na intuicji przy decydowaniu, co naprawić. Otwórz Profiler w React DevTools, aby dokładnie zobaczyć, które komponenty są renderowane i ile czasu zajmuje każdy z nich. W przypadku problemów wykraczających poza sam React, panel Performance w przeglądarce może ujawnić długotrwałe zadania, wolną eksploatację skryptów, kosztowne operacje układu lub wąskie gardła w renderowaniu. Zamiast zakładać, że „ten komponent działa wolno”, użyj tych narzędzi, aby ustalić dokładną przyczynę wolności.
Potwierdzenie, że naprawa rzeczywiście pomogła
Po zastosowaniu optymalizacji zmierz wyniki ponownie, zamiast zakładać, że to zadziałało. Sprawdź, czy czas renderowania rzeczywiście się skrócił, czy plik nie wymaga już tyle miejsca oraz czy interakcje są bardziej responsywne. Jeśli nic z tego się nie poprawiło, zmiana mogła być w ogóle niepotrzebna.
List kontrolny przed publikacją
Zanim wydasz aplikację React, sprawdź, czy nie renderujesz interfejsu, którego nie potrzebujesz, czy stan znajduje się we właściwym miejscu, czy nie przechowujesz wartości, które można by zamiast tego obliczyć, czy duże listy są obsługiwane efektywnie, czy ciężki kod ładuje się tylko wtedy, gdy jest to konieczne, czy wyszukiwanie i interakcje pozostają responsywne, czy nie wysyłasz niepotrzebnych żądań API, czy memoizacja rozwiązuje rzeczywisty problem, czy React Compiler mógłby przejąć zadanie optymalizacji, czy twoje klucze są stabilne, czy zmierzyłeś rzeczywisty wąskie gardło oraz czy sprawdziłeś poprawę później.
Ostatecznie najlepsza optymalizacja to nie ta, która dodaje najwięcej kodu — to ta, która sprawia, że React wykona mniej niepotrzebnej pracy.
Literatura pokrewna
- Zrozumienie React Lanes: Jak bitmaski kodują priorytetaktualizacji — Dowiedz się, jak React używa bitmasek do kodowania kilku priorytetówaktualizacji w jeden liczbę całkowitą, oraz dlaczego operacje bitowe zastępują proste flagi logiczne przy planowaniu.
- Kompilator TypeScript w Go i wykonywanie natywne: Przewodnik po migracji — Poznaj, jak kompilator TypeScript oparty na Go oraz wykonywanie natywne w Node.js wpłyną na projekty React i Next.js, oraz co należy poprawić w pliku tsconfig już teraz.