React 19 Suspense i Concurrent Rendering: Streaming SSR oraz pytania rekrutacyjne 2026
Kompleksowy przewodnik po React 19 Suspense, concurrent rendering i streaming SSR. Poznaj use() API, useTransition, useDeferredValue oraz przygotuj się do rozmów rekrutacyjnych.

React 19 Suspense fundamentalnie zmienia sposób, w jaki aplikacje obsługują operacje asynchroniczne. Dostarcza deklaratywny model zarządzania stanami ładowania, obsługi błędów oraz pobierania danych. W połączeniu z concurrent rendering, Suspense umożliwia streaming SSR, który przyspiesza czas wyświetlenia treści, jednocześnie utrzymując responsywność interfejsu podczas intensywnych obliczeń.
Granice Suspense definiują, gdzie pojawiają się fallbacki ładowania. Concurrent rendering pozwala React przerywać niskopriorytowe zadania, aby zachować responsywność interakcji o wysokim priorytecie (pisanie, klikanie). Razem umożliwiają progresywną hydrację i streamowanie HTML z serwera.
Różnice między Concurrent Rendering a synchronicznym React
Przed React 18 renderowanie było synchroniczne: gdy React rozpoczął renderowanie drzewa komponentów, musiał je ukończyć przed obsługą jakiejkolwiek innej pracy. Concurrent rendering łamie ten model, pozwalając React wstrzymywać, przerywać i wznawiać renderowanie w oparciu o priorytety.
Dokumentacja React opisuje concurrent rendering jako mechanizm działający w tle, który umożliwia funkcje takie jak Suspense, transitions i streaming SSR. Kluczowa idea: React może przygotowywać wiele wersji UI jednocześnie.
import { useState, useTransition } from 'react';
function SearchResults({ query }: { query: string }) {
// Kosztowne obliczenia lub pobieranie danych
const results = searchDatabase(query);
return <ResultsList items={results} />;
}
export function SearchPage() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const value = e.target.value;
// Wysoki priorytet: natychmiastowa aktualizacja inputa
setQuery(value);
// Niski priorytet: aktualizacja wyników w tle
startTransition(() => {
setDeferredQuery(value);
});
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<SearchResults query={deferredQuery} />
</div>
);
}Hook useTransition oznacza aktualizacje stanu jako niepilne. React kontynuuje wyświetlanie poprzedniego UI podczas obliczania nowego w tle, a następnie podmienia je, gdy jest gotowe.
Granice Suspense i hierarchia ładowania
Granice Suspense definiują fallbackowe UI dla komponentów, które się zawieszają. Gdy komponent wewnątrz granicy Suspense rzuca Promise (sygnalizując, że dane się ładują), React wyświetla fallback do momentu rozwiązania Promise.
import { Suspense } from 'react';
function ProductDetails({ id }: { id: string }) {
// Ten komponent zawiesza się podczas pobierania
const product = use(fetchProduct(id));
return <div>{product.name}</div>;
}
function ProductReviews({ id }: { id: string }) {
const reviews = use(fetchReviews(id));
return <ReviewsList reviews={reviews} />;
}
export function ProductPage({ id }: { id: string }) {
return (
<div>
{/* Zewnętrzna granica: łapie oba jeśli potrzeba */}
<Suspense fallback={<PageSkeleton />}>
{/* Wewnętrzna granica: recenzje mogą się ładować niezależnie */}
<ProductDetails id={id} />
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={id} />
</Suspense>
</Suspense>
</div>
);
}Zagnieżdżone granice Suspense umożliwiają granularną kontrolę stanów ładowania. Szczegóły produktu mogą się renderować zaraz po załadowaniu, podczas gdy recenzje pokazują własny szkielet.
API use() do pobierania danych
React 19 wprowadza API use(), udokumentowane w React RFC, jako oficjalny sposób odczytywania Promise i kontekstu podczas renderowania. W przeciwieństwie do hooków, use() może być wywoływane warunkowo.
import { use, Suspense } from 'react';
// Promise utworzony poza komponentem (render-as-you-fetch)
const userPromise = fetchUser(userId);
function UserProfile() {
// use() zawiesza się do rozwiązania Promise
const user = use(userPromise);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
export function UserPage() {
return (
<Suspense fallback={<ProfileSkeleton />}>
<UserProfile />
</Suspense>
);
}Wzorzec render-as-you-fetch rozpoczyna pobieranie danych przed rozpoczęciem renderowania, typowo w loaderach tras lub komponentach serwerowych. Zachowuje to równoległość, ponieważ wiele pobrań może działać jednocześnie zamiast kaskadowo.
Gotowy na rozmowy o React / Next.js?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Streaming SSR z React Server Components
Streaming SSR wysyła HTML do klienta w fragmentach, gdy dane stają się dostępne. W połączeniu z React Server Components, to podejście zapewnia szybsze pierwsze wyświetlenie treści przy progresywnej hydracji interaktywnych części.
import { Suspense } from 'react';
// Server Component: działa tylko na serwerze
async function ProductInfo({ id }: { id: string }) {
const product = await db.products.findUnique({ where: { id } });
return (
<section>
<h1>{product.name}</h1>
<p>{product.description}</p>
</section>
);
}
// Server Component z wolnymi danymi
async function RelatedProducts({ categoryId }: { categoryId: string }) {
const products = await db.products.findMany({
where: { categoryId },
take: 6
});
return <ProductGrid products={products} />;
}
export default async function ProductPage({
params
}: {
params: { id: string }
}) {
const product = await db.products.findUnique({
where: { id: params.id }
});
return (
<main>
{/* Streamuje natychmiast */}
<ProductInfo id={params.id} />
{/* Streamuje gdy gotowe, najpierw pokazuje szkielet */}
<Suspense fallback={<GridSkeleton />}>
<RelatedProducts categoryId={product.categoryId} />
</Suspense>
</main>
);
}Serwer najpierw streamuje HTML ProductInfo. Gdy dane RelatedProducts są gotowe, React wysyła dodatkowy HTML z instrukcjami zastąpienia szkieletu.
useDeferredValue dla kosztownych obliczeń
Podczas gdy useTransition opakowuje aktualizacje stanu, useDeferredValue opóźnia renderowanie konkretnej wartości. Sprawdza się przy filtrowaniu dużych list lub kosztownych obliczeniach pochodnych.
import { useState, useDeferredValue, useMemo } from 'react';
function ExpensiveList({ filter }: { filter: string }) {
// Kosztowna operacja filtrowania
const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
}, [filter]);
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
export function SearchableList() {
const [filter, setFilter] = useState('');
// Opóźniona wersja pozostaje w tyle podczas pisania
const deferredFilter = useDeferredValue(filter);
const isStale = filter !== deferredFilter;
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Szukaj..."
/>
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<ExpensiveList filter={deferredFilter} />
</div>
</div>
);
}Input pozostaje responsywny, ponieważ React priorytetyzuje aktualizację stanu. Lista renderuje się z opóźnioną wartością, gdy React ma wolny czas.
Pytania rekrutacyjne: Concurrent Rendering
Rozmowy techniczne na stanowiska React często badają zrozumienie funkcji concurrent. Te pytania oddzielają kandydatów, którzy wdrożyli produkcyjny kod Suspense, od tych, którzy znają tylko teorię.
P: Co dzieje się, gdy komponent się zawiesza?
Gdy komponent rzuca Promise podczas renderowania, React przechwytuje go na najbliższej granicy Suspense. React renderuje fallbackowe UI i subskrybuje Promise. Gdy się rozwiązuje, React ponownie renderuje zawieszony komponent. Jeśli Promise zostanie odrzucony, błąd propaguje się do najbliższej granicy błędów.
P: Dlaczego render-as-you-fetch ma znaczenie dla Suspense?
Pobieranie podczas renderowania tworzy kaskady zapytań: Parent pobiera, następnie renderuje Child, który pobiera, następnie renderuje Grandchild, który pobiera. Każdy krok czeka na poprzedni. Render-as-you-fetch rozpoczyna wszystkie pobrania przed renderowaniem, więc działają równolegle. Loadery tras i komponenty serwerowe naturalnie wspierają ten wzorzec.
P: Kiedy useTransition może zaszkodzić wydajności?
Transitions utrzymują stare UI widoczne podczas obliczania nowego. Oznacza to, że React renderuje dwukrotnie: raz dla nieaktualnego UI i raz dla świeżego UI. Dla szybkich operacji ten narzut przewyższa korzyści. Używaj transitions tylko dla aktualizacji trwających zauważalnie długo, typowo 100ms lub więcej.
Error Boundaries z Suspense
Suspense obsługuje stany ładowania, ale error boundaries obsługują awarie. Aplikacje produkcyjne potrzebują obu:
import { Component, Suspense, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: ReactNode;
errorFallback: ReactNode;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return this.props.errorFallback;
}
return this.props.children;
}
}
export function DataBoundary({
children,
fallback,
errorFallback
}: Props) {
return (
<ErrorBoundary errorFallback={errorFallback}>
<Suspense fallback={fallback}>
{children}
</Suspense>
</ErrorBoundary>
);
}Error boundary opakowuje granicę Suspense, więc odrzucone Promise uruchamiają UI błędu zamiast powodować crash aplikacji.
Suspense Batching w React 19.2
React 19.2 wprowadził batching Suspense dla renderowania serwerowego, rozwiązując częsty problem: wiele siostrzanych komponentów zawieszających się powodowało wiele round tripów. Przy batchingu React czeka krótko, aby zebrać wiele zawieszeń, a następnie rozwiązuje je razem.
// Po 19.2: React batchuje pobliskie zawieszenia
export function Dashboard() {
return (
<div className="grid grid-cols-3 gap-4">
{/* Te teraz są batchowane razem */}
<Suspense fallback={<CardSkeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<UsersCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<OrdersCard />
</Suspense>
</div>
);
}Batching redukuje wizualne migotanie kart pojawiających się jedna po drugiej i poprawia postrzeganą wydajność, prezentując powiązane dane razem.
Wzorce wydajnościowe dla produkcji
Te wzorce adresują typowe problemy produkcyjne z Suspense i concurrent rendering.
Unikaj kaskad Suspense
// Problem: Sekwencyjne pobieranie
function Parent() {
const data = use(fetchParent());
return <Child parentId={data.id} />; // Child pobiera po Parent
}
// Rozwiązanie: Równoległe pobieranie z Promise.all
const [parentPromise, childPromise] = [
fetchParent(),
fetchChild()
];
function OptimizedParent() {
const parent = use(parentPromise);
return <OptimizedChild promise={childPromise} />;
}Preload przy hover
function LinkWithPrefetch({ href, children }: Props) {
const router = useRouter();
function handleMouseEnter() {
// Rozpocznij pobieranie przed kliknięciem
router.prefetch(href);
}
return (
<Link href={href} onMouseEnter={handleMouseEnter}>
{children}
</Link>
);
}Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Kluczowe wzorce dla funkcji concurrent w React 19
- Umieszczaj granice Suspense na szwach UI, gdzie niezależne ładowanie ma sens, nie wokół każdego komponentu async
- Uruchamiaj pobieranie danych w loaderach tras lub komponentach serwerowych, nie wewnątrz funkcji render, aby umożliwić równoległe zapytania
- Używaj
useTransitiondla nawigacji i submitów formularzy inicjowanych przez użytkownika, które trwają ponad 100ms - Stosuj
useDeferredValuedla kosztownych obliczeń pochodnych jak filtrowanie ponad 10 000 elementów - Łącz error boundaries z granicami Suspense, aby odrzucone Promise pokazywały UI błędu zamiast crashować
- Preferuj streaming SSR dla stron z dużą ilością treści, gdzie czas do pierwszego bajtu ma większe znaczenie niż pełna hydracja
- Testuj funkcje concurrent z React DevTools Profiler, aby identyfikować niepotrzebne re-rendery podczas transitions
Znajdziesz błąd w React / Next.js?
Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Autor:
Anthony Fillion-MailletZałożyciel SharpSkill
Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.
Zaktualizowano 21 sierpnia 2026
Tagi
Udostępnij
Powiązane artykuły

Cache Components w Next.js 16: use cache, PPR i pytania rekrutacyjne na 2026 rok
Kompletny przewodnik po Cache Components w Next.js 16: dyrektywa use cache, Partial Pre-Rendering, cacheLife, cacheTag, Instant Navigations w 16.3 oraz pytania na rozmowę kwalifikacyjną.

React Compiler w 2026: automatyczna memoizacja i pytania rekrutacyjne
Kompletny przewodnik po React Compiler — automatyczna memoizacja, pipeline kompilacji, reguły React, integracja z ESLint i pytania na rozmowy kwalifikacyjne dla React w 2026 roku.

Server Actions w Next.js 16 w 2026: mutacje, rewalidacja i pytania rekrutacyjne
Jak Server Actions w Next.js 16 obsługują mutacje, rewalidację, stan oczekiwania, optymistyczny interfejs i bezpieczeństwo, wraz z pytaniami rekrutacyjnymi sprawdzającymi każdą koncepcję.