# React 19 Suspense та Concurrent Rendering: Streaming SSR і питання для співбесід 2026 > Повний посібник з React 19 Suspense, concurrent rendering та streaming SSR. Вивчіть use() API, useTransition, useDeferredValue та підготуйтеся до технічних співбесід. - 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 докорінно змінює спосіб, яким застосунки обробляють асинхронні операції. Він надає декларативну модель для керування станами завантаження, обробки помилок та отримання даних. У поєднанні з concurrent rendering, Suspense забезпечує streaming SSR, який прискорює відображення контенту, зберігаючи чутливість інтерфейсу під час інтенсивних обчислень. > **Ключова інформація** > > Межі Suspense визначають, де з'являються fallback-и завантаження. Concurrent rendering дозволяє React переривати низькопріоритетні завдання, щоб зберегти чутливість високопріоритетних взаємодій (введення, кліки). Разом вони забезпечують прогресивну гідрацію та потокову передачу HTML із сервера. ## Відмінності між Concurrent Rendering та синхронним React До React 18 рендеринг був синхронним: коли React починав рендерити дерево компонентів, він мусив завершити його перед обробкою будь-якої іншої роботи. Concurrent rendering ламає цю модель, дозволяючи React призупиняти, переривати та відновлювати рендеринг на основі пріоритетів. [Документація React](https://react.dev/blog/2022/03/29/react-v18#what-is-concurrent-react) описує concurrent rendering як фоновий механізм, що забезпечує такі функції, як Suspense, transitions та streaming SSR. Ключова ідея: React може готувати кілька версій UI одночасно. ```tsx // TransitionExample.tsx import { useState, useTransition } from 'react'; function SearchResults({ query }: { query: string }) { // Витратні обчислення або отримання даних 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; // Високий пріоритет: негайне оновлення input setQuery(value); // Низький пріоритет: оновлення результатів у фоні startTransition(() => { setDeferredQuery(value); }); } return (
{isPending && }
); } ``` Хук `useTransition` позначає оновлення стану як неневідкладні. React продовжує показувати попередній UI, обчислюючи новий у фоні, а потім замінює його, коли готовий. ## Межі Suspense та ієрархія завантаження Межі Suspense визначають fallback UI для компонентів, що призупиняються. Коли компонент всередині межі Suspense кидає Promise (сигналізуючи, що дані завантажуються), React показує fallback до розв'язання Promise. ```tsx // ProductPage.tsx import { Suspense } from 'react'; function ProductDetails({ id }: { id: string }) { // Цей компонент призупиняється під час отримання 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 (
{/* Зовнішня межа: ловить обидва за потреби */} }> {/* Внутрішня межа: відгуки можуть завантажуватись незалежно */} }>
); } ``` Вкладені межі Suspense забезпечують гранулярний контроль станів завантаження. Деталі продукту можуть рендеритись одразу після завантаження, тоді як відгуки показують власний скелетон. ## API use() для отримання даних React 19 представляє API `use()`, задокументоване в [React RFC](https://github.com/reactjs/rfcs/blob/main/text/0229-use.md), як офіційний спосіб читання Promise та контексту під час рендерингу. На відміну від хуків, `use()` можна викликати умовно. ```tsx // UserProfile.tsx import { use, Suspense } from 'react'; // Promise створений поза компонентом (render-as-you-fetch) const userPromise = fetchUser(userId); function UserProfile() { // use() призупиняється до розв'язання Promise const user = use(userPromise); return (

{user.name}

{user.email}

); } export function UserPage() { return ( }> ); } ``` Патерн render-as-you-fetch починає отримання даних до початку рендерингу, зазвичай у завантажувачах маршрутів або серверних компонентах. Це зберігає паралелізм, оскільки кілька запитів можуть виконуватись одночасно, а не каскадно. ## Streaming SSR з React Server Components Streaming SSR надсилає HTML клієнту частинами, коли дані стають доступними. У поєднанні з [React Server Components](/blog/react-next/react-server-components-patterns-pitfalls), цей підхід забезпечує швидше перше відображення контенту з прогресивною гідрацією інтерактивних частин. ```tsx // app/products/[id]/page.tsx (Next.js App Router) import { Suspense } from 'react'; // Server Component: працює тільки на сервері async function ProductInfo({ id }: { id: string }) { const product = await db.products.findUnique({ where: { id } }); return (

{product.name}

{product.description}

); } // Server Component з повільними даними 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 (
{/* Потік починається негайно */} {/* Потік коли готово, спочатку показує скелетон */} }>
); } ``` Сервер спочатку передає HTML ProductInfo. Коли дані RelatedProducts готові, React надсилає додатковий HTML з інструкціями для заміни скелетона. ## useDeferredValue для витратних обчислень Поки `useTransition` обгортає оновлення стану, `useDeferredValue` відкладає рендеринг конкретного значення. Це добре працює для фільтрації великих списків або витратних похідних обчислень. ```tsx // FilterableList.tsx 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 (
    {filteredItems.map(item => (
  • {item.name}
  • ))}
); } export function SearchableList() { const [filter, setFilter] = useState(''); // Відкладена версія відстає під час введення const deferredFilter = useDeferredValue(filter); const isStale = filter !== deferredFilter; return (
setFilter(e.target.value)} placeholder="Пошук..." />
); } ``` Input залишається чутливим, оскільки React пріоритизує оновлення стану. Список рендериться з відкладеним значенням, коли React має вільний час. ## Питання для співбесід: Concurrent Rendering Технічні співбесіди на [позиції React](/technologies/react-next/interview-questions/react-performance-optimization) часто перевіряють розуміння 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](/technologies/react-next/interview-questions/react-error-boundaries) обробляють помилки. Продакшн застосунки потребують обох: ```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 обгортає межу Suspense, тому відхилені Promise запускають UI помилки замість краху застосунку. ## Suspense Batching у React 19.2 React 19.2 представив batching Suspense для серверного рендерингу, вирішуючи поширену проблему: кілька сусідніх компонентів, що призупиняються, спричиняли кілька round trip-ів. З batching React коротко чекає, щоб зібрати кілька призупинень, а потім розв'язує їх разом. ```tsx // До 19.2: Кожна межа Suspense передає потік незалежно // Після 19.2: React групує близькі призупинення export function Dashboard() { return (
{/* Ці тепер групуються разом */} }> }> }>
); } ``` Batching зменшує візуальне мерехтіння карток, що з'являються одна за одною, та покращує сприйняту продуктивність, представляючи пов'язані дані разом. ## Патерни продуктивності для продакшну Ці патерни вирішують поширені продакшн проблеми з Suspense та concurrent rendering. **Уникайте каскадів Suspense** ```tsx // Проблема: Послідовне отримання function Parent() { const data = use(fetchParent()); return ; // Child отримує після Parent } // Рішення: Паралельне отримання з Promise.all const [parentPromise, childPromise] = [ fetchParent(), fetchChild() ]; function OptimizedParent() { const parent = use(parentPromise); return ; } ``` **Preload при hover** ```tsx // LinkWithPrefetch.tsx function LinkWithPrefetch({ href, children }: Props) { const router = useRouter(); function handleMouseEnter() { // Почати отримання до кліку router.prefetch(href); } return ( {children} ); } ``` ## Ключові патерни для concurrent функцій React 19 - Розміщуйте межі Suspense на швах UI, де незалежне завантаження має сенс, а не навколо кожного async компонента - Запускайте отримання даних у завантажувачах маршрутів або серверних компонентах, а не всередині функцій render, щоб забезпечити паралельні запити - Використовуйте `useTransition` для навігацій та відправок форм, ініційованих користувачем, що тривають понад 100мс - Застосовуйте `useDeferredValue` для витратних похідних обчислень, як-от фільтрація понад 10 000 елементів - Поєднуйте error boundaries з межами Suspense, щоб відхилені Promise показували UI помилки замість краху - Віддавайте перевагу streaming SSR для сторінок з великою кількістю контенту, де час до першого байта важливіший за повну гідрацію - Тестуйте concurrent функції з React DevTools Profiler для виявлення непотрібних повторних рендерів під час transitions --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/react-next/react-19-suspense-concurrent-rendering-streaming-ssr