React 19 Suspense та Concurrent Rendering: Streaming SSR і питання для співбесід 2026
Повний посібник з React 19 Suspense, concurrent rendering та streaming SSR. Вивчіть use() API, useTransition, useDeferredValue та підготуйтеся до технічних співбесід.

React 19 Suspense докорінно змінює спосіб, яким застосунки обробляють асинхронні операції. Він надає декларативну модель для керування станами завантаження, обробки помилок та отримання даних. У поєднанні з concurrent rendering, Suspense забезпечує streaming SSR, який прискорює відображення контенту, зберігаючи чутливість інтерфейсу під час інтенсивних обчислень.
Межі Suspense визначають, де з'являються fallback-и завантаження. Concurrent rendering дозволяє React переривати низькопріоритетні завдання, щоб зберегти чутливість високопріоритетних взаємодій (введення, кліки). Разом вони забезпечують прогресивну гідрацію та потокову передачу HTML із сервера.
Відмінності між Concurrent Rendering та синхронним React
До React 18 рендеринг був синхронним: коли React починав рендерити дерево компонентів, він мусив завершити його перед обробкою будь-якої іншої роботи. Concurrent rendering ламає цю модель, дозволяючи React призупиняти, переривати та відновлювати рендеринг на основі пріоритетів.
Документація React описує concurrent rendering як фоновий механізм, що забезпечує такі функції, як Suspense, transitions та streaming SSR. Ключова ідея: React може готувати кілька версій UI одночасно.
import { useState, useTransition } from 'react';
function SearchResults({ query }: { query: string }) {
// Витратні обчислення або отримання даних
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;
// Високий пріоритет: негайне оновлення input
setQuery(value);
// Низький пріоритет: оновлення результатів у фоні
startTransition(() => {
setDeferredQuery(value);
});
}
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<SearchResults query={deferredQuery} />
</div>
);
}Хук useTransition позначає оновлення стану як неневідкладні. React продовжує показувати попередній UI, обчислюючи новий у фоні, а потім замінює його, коли готовий.
Межі Suspense та ієрархія завантаження
Межі Suspense визначають fallback UI для компонентів, що призупиняються. Коли компонент всередині межі Suspense кидає Promise (сигналізуючи, що дані завантажуються), React показує fallback до розв'язання Promise.
import { Suspense } from 'react';
function ProductDetails({ id }: { id: string }) {
// Цей компонент призупиняється під час отримання
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>
{/* Зовнішня межа: ловить обидва за потреби */}
<Suspense fallback={<PageSkeleton />}>
{/* Внутрішня межа: відгуки можуть завантажуватись незалежно */}
<ProductDetails id={id} />
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={id} />
</Suspense>
</Suspense>
</div>
);
}Вкладені межі Suspense забезпечують гранулярний контроль станів завантаження. Деталі продукту можуть рендеритись одразу після завантаження, тоді як відгуки показують власний скелетон.
API use() для отримання даних
React 19 представляє API use(), задокументоване в React RFC, як офіційний спосіб читання Promise та контексту під час рендерингу. На відміну від хуків, use() можна викликати умовно.
import { use, Suspense } from 'react';
// Promise створений поза компонентом (render-as-you-fetch)
const userPromise = fetchUser(userId);
function UserProfile() {
// use() призупиняється до розв'язання 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>
);
}Патерн render-as-you-fetch починає отримання даних до початку рендерингу, зазвичай у завантажувачах маршрутів або серверних компонентах. Це зберігає паралелізм, оскільки кілька запитів можуть виконуватись одночасно, а не каскадно.
Готовий до співбесід з React / Next.js?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Streaming SSR з React Server Components
Streaming SSR надсилає HTML клієнту частинами, коли дані стають доступними. У поєднанні з React Server Components, цей підхід забезпечує швидше перше відображення контенту з прогресивною гідрацією інтерактивних частин.
import { Suspense } from 'react';
// Server Component: працює тільки на сервері
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 з повільними даними
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>
{/* Потік починається негайно */}
<ProductInfo id={params.id} />
{/* Потік коли готово, спочатку показує скелетон */}
<Suspense fallback={<GridSkeleton />}>
<RelatedProducts categoryId={product.categoryId} />
</Suspense>
</main>
);
}Сервер спочатку передає HTML ProductInfo. Коли дані RelatedProducts готові, React надсилає додатковий HTML з інструкціями для заміни скелетона.
useDeferredValue для витратних обчислень
Поки useTransition обгортає оновлення стану, useDeferredValue відкладає рендеринг конкретного значення. Це добре працює для фільтрації великих списків або витратних похідних обчислень.
import { useState, useDeferredValue, useMemo } from 'react';
function ExpensiveList({ filter }: { filter: string }) {
// Витратна операція фільтрації
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('');
// Відкладена версія відстає під час введення
const deferredFilter = useDeferredValue(filter);
const isStale = filter !== deferredFilter;
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Пошук..."
/>
<div style={{ opacity: isStale ? 0.7 : 1 }}>
<ExpensiveList filter={deferredFilter} />
</div>
</div>
);
}Input залишається чутливим, оскільки React пріоритизує оновлення стану. Список рендериться з відкладеним значенням, коли React має вільний час.
Питання для співбесід: Concurrent Rendering
Технічні співбесіди на позиції React часто перевіряють розуміння concurrent функцій. Ці питання відрізняють кандидатів, які впроваджували продакшн код з Suspense, від тих, хто знає лише теорію.
П: Що відбувається, коли компонент призупиняється?
Коли компонент кидає Promise під час рендерингу, React перехоплює його на найближчій межі Suspense. React рендерить fallback UI та підписується на Promise. Коли він розв'язується, React повторно рендерить призупинений компонент. Якщо Promise відхиляється, помилка поширюється до найближчої межі помилок.
П: Чому render-as-you-fetch важливий для Suspense?
Отримання під час рендерингу створює каскади запитів: Parent отримує, потім рендерить Child, який отримує, потім рендерить Grandchild, який отримує. Кожен крок чекає попередній. Render-as-you-fetch починає всі запити до рендерингу, тому вони виконуються паралельно. Завантажувачі маршрутів і серверні компоненти природно підтримують цей патерн.
П: Коли useTransition може зашкодити продуктивності?
Transitions зберігають старий UI видимим під час обчислення нового. Це означає, що React рендерить двічі: один раз для застарілого UI і один раз для свіжого. Для швидких операцій ці накладні витрати перевищують користь. Використовуйте transitions лише для оновлень, що займають помітний час, зазвичай 100мс або більше.
Error Boundaries з Suspense
Suspense обробляє стани завантаження, але error boundaries обробляють помилки. Продакшн застосунки потребують обох:
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 обгортає межу Suspense, тому відхилені Promise запускають UI помилки замість краху застосунку.
Suspense Batching у React 19.2
React 19.2 представив batching Suspense для серверного рендерингу, вирішуючи поширену проблему: кілька сусідніх компонентів, що призупиняються, спричиняли кілька round trip-ів. З batching React коротко чекає, щоб зібрати кілька призупинень, а потім розв'язує їх разом.
// Після 19.2: React групує близькі призупинення
export function Dashboard() {
return (
<div className="grid grid-cols-3 gap-4">
{/* Ці тепер групуються разом */}
<Suspense fallback={<CardSkeleton />}>
<RevenueCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<UsersCard />
</Suspense>
<Suspense fallback={<CardSkeleton />}>
<OrdersCard />
</Suspense>
</div>
);
}Batching зменшує візуальне мерехтіння карток, що з'являються одна за одною, та покращує сприйняту продуктивність, представляючи пов'язані дані разом.
Патерни продуктивності для продакшну
Ці патерни вирішують поширені продакшн проблеми з Suspense та concurrent rendering.
Уникайте каскадів Suspense
// Проблема: Послідовне отримання
function Parent() {
const data = use(fetchParent());
return <Child parentId={data.id} />; // Child отримує після Parent
}
// Рішення: Паралельне отримання з Promise.all
const [parentPromise, childPromise] = [
fetchParent(),
fetchChild()
];
function OptimizedParent() {
const parent = use(parentPromise);
return <OptimizedChild promise={childPromise} />;
}Preload при hover
function LinkWithPrefetch({ href, children }: Props) {
const router = useRouter();
function handleMouseEnter() {
// Почати отримання до кліку
router.prefetch(href);
}
return (
<Link href={href} onMouseEnter={handleMouseEnter}>
{children}
</Link>
);
}Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Ключові патерни для concurrent функцій React 19
- Розміщуйте межі Suspense на швах UI, де незалежне завантаження має сенс, а не навколо кожного async компонента
- Запускайте отримання даних у завантажувачах маршрутів або серверних компонентах, а не всередині функцій render, щоб забезпечити паралельні запити
- Використовуйте
useTransitionдля навігацій та відправок форм, ініційованих користувачем, що тривають понад 100мс - Застосовуйте
useDeferredValueдля витратних похідних обчислень, як-от фільтрація понад 10 000 елементів - Поєднуйте error boundaries з межами Suspense, щоб відхилені Promise показували UI помилки замість краху
- Віддавайте перевагу streaming SSR для сторінок з великою кількістю контенту, де час до першого байта важливіший за повну гідрацію
- Тестуйте concurrent функції з React DevTools Profiler для виявлення непотрібних повторних рендерів під час transitions
Чи знайдеш ти помилку в React / Next.js?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 21 серпня 2026 р.
Теги
Поділитися
Пов'язані статті

Cache Components у Next.js 16: use cache, PPR та питання для співбесід у 2026 році
Повний посібник з Cache Components у Next.js 16: директива use cache, Partial Pre-Rendering, cacheLife, cacheTag, Instant Navigations у 16.3 та питання для senior-розробників на співбесідах.

React Compiler у 2026 році: автоматична мемоїзація та питання для співбесід
Повний огляд React Compiler — автоматична мемоїзація, конвеєр компіляції, правила React, інтеграція з ESLint та питання для технічних співбесід з React у 2026 році.

Server Actions у Next.js 16 у 2026: мутації, ревалідація та питання співбесід
Як Server Actions у Next.js 16 обробляють мутації, ревалідацію, стан очікування, оптимістичний інтерфейс і безпеку, разом із питаннями співбесід на кожну концепцію.