# 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.
- Published: 2026-08-21
- Updated: 2026-08-21
- Author: Anthony Fillion-Maillet
- Tags: react, suspense, concurrent-rendering, streaming-ssr, interview
- Reading time: 12 min
---
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ń.
> **Kluczowe informacje**
>
> 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](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-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.
```tsx
// TransitionExample.tsx
import { useState, useTransition } from 'react';
function SearchResults({ query }: { query: string }) {
// Kosztowne obliczenia lub pobieranie danych
const results = searchDatabase(query);
return ;
}
export function SearchPage() {
const [query, setQuery] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent) {
const value = e.target.value;
// Wysoki priorytet: natychmiastowa aktualizacja inputa
setQuery(value);
// Niski priorytet: aktualizacja wyników w tle
startTransition(() => {
setDeferredQuery(value);
});
}
return (
{isPending && }
);
}
```
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.
```tsx
// ProductPage.tsx
import { Suspense } from 'react';
function ProductDetails({ id }: { id: string }) {
// Ten komponent zawiesza się podczas pobierania
const product = use(fetchProduct(id));
return
{product.name}
;
}
function ProductReviews({ id }: { id: string }) {
const reviews = use(fetchReviews(id));
return ;
}
export function ProductPage({ id }: { id: string }) {
return (
{/* Zewnętrzna granica: łapie oba jeśli potrzeba */}
}>
{/* Wewnętrzna granica: recenzje mogą się ładować niezależnie */}
}>
);
}
```
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](https://github.com/reactjs/rfcs/blob/main/text/0229-use.md), jako oficjalny sposób odczytywania Promise i kontekstu podczas renderowania. W przeciwieństwie do hooków, `use()` może być wywoływane warunkowo.
```tsx
// UserProfile.tsx
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 (
{user.name}
{user.email}
);
}
export function UserPage() {
return (
}>
);
}
```
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.
## 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](/blog/react-next/react-server-components-patterns-pitfalls), to podejście zapewnia szybsze pierwsze wyświetlenie treści przy progresywnej hydracji interaktywnych części.
```tsx
// app/products/[id]/page.tsx (Next.js App Router)
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 (
{product.name}
{product.description}
);
}
// Server Component z wolnymi danymi
async function RelatedProducts({ categoryId }: { categoryId: string }) {
const products = await db.products.findMany({
where: { categoryId },
take: 6
});
return ;
}
export default async function ProductPage({
params
}: {
params: { id: string }
}) {
const product = await db.products.findUnique({
where: { id: params.id }
});
return (
{/* Streamuje natychmiast */}
{/* Streamuje gdy gotowe, najpierw pokazuje szkielet */}
}>
);
}
```
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.
```tsx
// FilterableList.tsx
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 (
{filteredItems.map(item => (
{item.name}
))}
);
}
export function SearchableList() {
const [filter, setFilter] = useState('');
// Opóźniona wersja pozostaje w tyle podczas pisania
const deferredFilter = useDeferredValue(filter);
const isStale = filter !== deferredFilter;
return (
);
}
```
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](/technologies/react-next/interview-questions/react-performance-optimization) 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](/technologies/react-next/interview-questions/react-error-boundaries) obsługują awarie. Aplikacje produkcyjne potrzebują obu:
```tsx
// DataBoundary.tsx
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 {
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 (
{children}
);
}
```
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.
```tsx
// Przed 19.2: Każda granica Suspense streamuje niezależnie
// Po 19.2: React batchuje pobliskie zawieszenia
export function Dashboard() {
return (
{/* Te teraz są batchowane razem */}
}>
}>
}>
);
}
```
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**
```tsx
// Problem: Sekwencyjne pobieranie
function Parent() {
const data = use(fetchParent());
return ; // 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 ;
}
```
**Preload przy hover**
```tsx
// LinkWithPrefetch.tsx
function LinkWithPrefetch({ href, children }: Props) {
const router = useRouter();
function handleMouseEnter() {
// Rozpocznij pobieranie przed kliknięciem
router.prefetch(href);
}
return (
{children}
);
}
```
## 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 `useTransition` dla nawigacji i submitów formularzy inicjowanych przez użytkownika, które trwają ponad 100ms
- Stosuj `useDeferredValue` dla 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
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/pl/blog/react-next/react-19-suspense-concurrent-rendering-streaming-ssr