Wyjaśnienie częściowego uprzedniego renderowania i równoczesnego renderowania
Dowiedz się, w jaki sposób częściowe przedrenderowanie w Next.js oraz równoczesne renderowanie w React naprawiają wolne aplikacje, pozwalając frameworkom planować i przekazywać zadania stopniowo, zamiast traktować procesy renderowania jako jedną blokującą całość.
Współczesne działania na rzecz poprawy wydajności w React i Next.js zawsze wracają do tego samego wniosku: to przymus, by cała strona lub całe renderowanie funkcjonowały jako jedna, nieprzerwana jednostka, sprawia, że aplikacje wydają się wolne. Dwie techniki adresują ten problem z różnych perspektyw – częściowe pre-renderowanie, które umożliwia pojedynczej trasie Next.js łączenie treści statycznych i dynamicznych, oraz renderowanie równoległe, które pozwala React przerwać i przeorganizować zadania w przeglądarce. Razem pokazują one wzorzec wart zrozumienia: poprawa wydajności coraz częściej nie wynika z pisania „szybszego” kodu, lecz z umożliwienia frameworkowi inteligentniejszego planowania i realizacji zadań.
Stara opcja renderowania – albo wszystko, albo nic
Przez długi czas strona w Next.js miała do wyboru dokładnie dwa tryby renderowania:
- Generowanie statyczne (SSG) — strony są szybko dostarczane, ponieważ są tworzone z góry, ale ich treść staje się przestarzała aż do kolejnej aktualizacji.
- Rendering po stronie serwera (SSR) — treść jest zawsze aktualna, ale każde żądanie musi czekać na naj wolniejsze dane przed wysłaniem jakiejkolwiek informacji.
Problem polega na tym, że większość rzeczywistych stron nie pasuje idealnie do żadnej z tych kategorii. Strona produktu na przykład jest w dużej mierze statyczna — układ, nawigacja i teksty marketingowe nie zmieniają się przy każdym żądaniu — ale zawiera również kilka elementów naprawdę dynamicznych, takich jak ikona koszyka czy licznik aktualnej ilości towaru. Renderowanie całej strony za pomocą SSR tylko po to, by utrzymać aktualny jeden mały element, oznacza ponoszenie pełnych kosztów renderowania po stronie serwera dla treści, które tego nie wymagają.
Zezwalanie, by jedna ścieżka była zarówno statyczna, jak i dynamiczna
Częściowe wstępne renderowanie (PPR) zostało stworzone po to, by zamknąć tę lukę. Pozwala ono pojedynczej ścieżce natychmiast wysłać statyczną strukturę, podczas gdy dynamiczne elementy w jej wnętrzu są streamowane, jak tylko ich dane zostaną uzyskane — bez osobnych stron i bez konieczności przechodzenia między getStaticProps a getServerSideProps.
// app/product/[id]/page.tsx
import { Suspense } from 'react';
import ProductShell from '@/components/ProductShell';
import LiveInventory from '@/components/LiveInventory';
export default function ProductPage({ params }: { params: { id: string } }) {
return (
<ProductShell productId={params.id}>
{/* static instantly, no waiting on the network */}
<Suspense fallback={<InventorySkeleton />}>
{/* streamed in once the dynamic data resolves */}
<LiveInventory productId={params.id} />
</Suspense>
</ProductShell>
);
}
Mechanizmem tym jest granica Suspense. Wszystko, co znajduje się poza nią, jest renderowane z góry podczas budowania aplikacji i dostarczane natychmiast; wszystko, co jest wewnątrz niej, jest obliczane i streamowane w momencie żądania. To właśnie stanowi cały model myślowy: jeden plik, jedna ścieżka, współistnienie dwóch strategii renderowania.
To rozwiązanie przynosi kilka konkretnych korzyści. Czas dostarczenia pierwszego bajtu się skraca, ponieważ statyczna struktura jest dostarczana bezpośrednio z serwera, zamiast być obliczana przy każdej prośbie. Nie ma też potrzeby stanu ładowania całej strony – odwiedzający natychmiast widzą istotne, statyczne części strony, a mniejsze elementy dynamiczne pojawiają się w miarę ich przetwarzania. Ponadto obciążenie poznawcze jest mniejsze niż przy utrzymywaniu oddzielnych stron statycznych i renderowanych na serwerze, ponieważ pracuje się z jedną ścieżką i jednym plikiem, który po prostu łączy różne strategie. Zespół Next.js opisuje ten cel jako dostarczanie zalet zarówno renderowania statycznego, jak i dynamicznego bez typowych kompromisów architektonicznych.
Kilka praktycznych wskazówek sprawia, że PPR jest skuteczny w produkcji. Utrzymuj granice Suspense w wąskim zakresie tylko wokół tych elementów, które są rzeczywiście dynamiczne — obejmowanie zbyt dużo treści unieważnia sens wstępnego renderowania. Zachowuj lekkość szkieletów awaryjnych, ponieważ one same stanowią część statycznie renderowanego obudowy. Jeśli używasz TypeScript, zdefiniuj jasne umowy właściwości pomiędzy statyczną obudową a komponentami streamowanymi, aby obie części pozostawały zsynchronizowane w miarę ich rozwoju. A podczas testowania ogranicz prędkość połączenia do poziomu wolnego 3G w narzędziach deweloperskich przeglądarki — zalety PPR stają się znacznie bardziej widoczne w realistycznych warunkach sieciowych niż przy szybkim połączeniu lokalnym.
Jeśli twoja zespół używa Next.js 14 lub nowszej wersji, warto wypróbować PPR na pojedynczej trasie przed wprowadzeniem go w całym aplikacji. Nie jest to termin marketingowy – to strategia renderowania, która w końcu odpowiada temu, jak faktycznie zachowują się rzeczywiste strony: częściowo statyczne, częściowo dynamiczne, jednocześnie.
Rozszerzenie tej samej koncepcji na renderowanie po stronie klienta
Częściowe pre-rendering rozwiązuje problem podziału na elementy statyczne i dynamiczne na poziomie serwera i sieci. Renderowanie równoległe, wprowadzone w React 18 i dalej udoskonalone w React 19, rozwiązuje podobny problem w przeglądarce: zamiast wybierać między „renderowaniem wszystkiego teraz” a „nie renderowaniem niczego na razie”, React może teraz natychmiast wyrenderować niektóre elementy, a inne pozostawić w oczekiwaniu.
Przed React 18 renderowanie było synchroniczne i blokujące. Jedna aktualizacja stanu wywoływała ponowne renderowanie, które musiało zostać ukończone bez względu na wszystko – nawet jeśli oznaczało to zatrzymanie przewijania lub wprowadzania danych, podczas gdy React przetwarzał całą strukturę.
Renderowanie równoległe zmienia to zachowanie. React może teraz wstrzymać proces renderowania w połowie, nadać priorytet pilnym aktualizacjom, takim jak pisanie lub klikanie, przed mniej ważnymi, np. filtrowaniem długiej listy, oraz odrzucić prace w trakcie wykonywania, jeśli nowsza aktualizacja sprawi, że staną się one bez znaczenia. Ważne jest to, że nie chodzi o nowy API, który trzeba byłoby uczyć od zera – jest to inny model planowania działający pod spodem hooków, których już używasz.
Hooki umożliwiające równoległe planowanie
useTransition pozwala oznaczyć aktualizację stanu jako mniej pilną, dzięki czemu React może utrzymać responsywność interfejsu, podczas gdy aktualizacja jest przetwarzana w tle.
function ProductSearch() {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
function handleChange(e) {
const value = e.target.value;
setQuery(value); // urgent — keep input snappy
startTransition(() => {
// non-urgent — can be interrupted
setResults(filterProducts(value));
});
}
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <span className="text-gray-400">Updating…</span>}
<ResultsList items={results} />
</>
);
}
Dzięki temu wzorcowi wpisywanie tekstu do pola wyszukiwania przebiega płynnie, nawet gdy w tle filtrowane są tysiące elementów.
Suspense pełni podobną rolę w przesyłaniu danych strumieniowo po stronie klienta, jaką pełni w PPR po stronie serwera: zamiast blokować całą stronę podczas ładowania danych, pozwala poszczególnym elementom interfejsu na niezależne rozwiązanie. W połączeniu z Next.js App Router umożliwia to również wykorzystanie komponentów serwerowych, które strumieniowo dodają się do strony, zamiast opóźniać całe ładowanie.
useDeferredValue rozwiązuje powiązany, lecz odrębny problem: kosztowne ponowne renderowanie spowodowane wartościami pochodzącymi z propów lub kontekstu, a nie ze stanu lokalnego komponentu. Pozwala to Reactowi odłożyć ponowne obliczanie tych kosztownych części na moment, gdy będzie miał wolny czas.
Dla zespołów pracujących z React, Next.js, TypeScript, Redux i Tailwind CSS ten model planowania ma znaczenie nie tylko w kontekście poszczególnych hooków — nowsze wzorce asynchroniczne w Redux Toolkit oraz sposób działania Next.js Server Actions w tle opierają się na tym samym podstawowym planowaniu równoległym, które obecnie dostarcza React. Równoległość to coś, co nie włącza się bezpośrednio; jest to funkcjonalność, którą React stosuje automatycznie, gdy komponenty są zorganizowane w taki sposób, że umożliwia to przerwanie i wznowienie ich renderowania.
Czego należy pamiętać
W obu tych technikach podstawowa lekcja jest ta sama: to traktowanie całej strony lub całego wyniku renderowania jako jednej nierozdzielnej jednostki powoduje wolne działanie, a niekoniecznie nieefektywny kod. Concurrent Rendering nie polega na pisaniu szybszego kodu — chodzi o mądrzejsze planowanie wykonywania już istniejącego kodu. W praktyce oznacza to odroczenie niepilnych aktualizacji stanu za pomocą useTransition, przesyłanie danych strumieniowo za pomocą Suspense oraz testowanie na urządzeniach o niższych parametrach, ponieważ właśnie tam korzyści z równoczesności są najbardziej widoczne. W połączeniu z Partial Pre-rendering po stronie serwera, te narzędzia umożliwiają pojedynczej ścieżce lub drzewu komponentów dostarczanie treści statycznych natychmiast, podczas gdy treści dynamiczne są przesyłane tylko wtedy, gdy są gotowe — to najlepsze z obu podejść do renderowania, bez konieczności przebudowy architektury wokół któregokolwiek z nich.
Literatura pokrewna
- Wnętrze Go Rewrite w TypeScript 7: Przyspieszenie bez zmian w kodzie — Dowiedz się, jak kompilator oparty na Go w TypeScript 7 zapewnia 8-12 razy szybsze budowanie aplikacji, dlaczego ta zmiana architektury jest skuteczna oraz jak bezpiecznie aktualizować istniejące projekty.
- Prymitywy SSR w React 19.2: Activity, cacheSignal i PPR wyjaśnione — Poznaj, jak nowy komponent Activity, cacheSignal oraz Partial Pre-rendering w React 19.2 dają programistom bezpośrednią kontrolę nad wydajnością renderowania na serwerze.