Пошук уроків, статей та іншого контенту
Застосовуйте Suspense для розділення повільних частин UI та поступового відображення готового контенту.
Suspense — це компонент React, який дозволяє показати запасний інтерфейс, поки вкладена частина UI ще не готова.
У Next.js Suspense особливо корисний разом із Server Components та потоковим рендерингом:
Next.js відразу надсилає браузеру готову частину сторінки.
Для повільної частини показується fallback.
Коли повільний компонент завершує роботу, його результат поступово додається до сторінки.
Завдяки цьому користувач не чекає на найповільніший запит, перш ніж побачити весь інтерфейс.
import { Suspense } from "react";
<Suspense fallback={<p>Завантаження...</p>}>
<SlowComponent />
</Suspense>fallback — це JSX, який відображається, поки SlowComponent призупинений.
У Next.js App Router Server Component може бути асинхронним. Якщо такий компонент очікує результат операції, Suspense може показувати fallback для цієї конкретної частини сторінки.
Розглянемо сторінку дашборда з двома повільними секціями:
import { Suspense } from "react";
export const dynamic = "force-dynamic";
const delay = (milliseconds: number) =>
new Promise<void>((resolve) => {
setTimeout(resolve, milliseconds);
});
function SectionFallback({ text }: { text: string }) {
return (
<section aria-busy="true">
<p>{text}</p>
</section>
);
}
async function Statistics() {
await delay(1000);
return (
<section>
<h2>Статистика</h2>
<ul>
<li>Нові користувачі: 128</li>
<li>Активні сесії: 47</li>
<li>Замовлення: 36</li>
</ul>
</section>
);
}
async function Recommendations() {
await delay(2000);
return (
<section>
<h2>Рекомендації</h2>
<ul>
<li>Оновіть опис профілю</li>
<li>Перегляньте звіт за цей місяць</li>
<li>Додайте резервний спосіб входу</li>
</ul>
</section>
);
}
export default function DashboardPage() {
return (
<main>
<h1>Дашборд</h1>
<p>Цей заголовок відображається одразу.</p>
<Suspense
fallback={
<SectionFallback text="Завантажуємо статистику..." />
}
>
<Statistics />
</Suspense>
<Suspense
fallback={
<SectionFallback text="Завантажуємо рекомендації..." />
}
>
<Recommendations />
</Suspense>
</main>
);
}У цьому прикладі:
заголовок сторінки відображається одразу;
через приблизно одну секунду з’являється Statistics;
через приблизно дві секунди з’являється Recommendations;
кожна секція має власний fallback;
повільні секції не блокують одна одну.
export const dynamic = "force-dynamic" змушує Next.js генерувати сторінку під час запиту. Це зручно для демонстрації потокового відображення. У реальному застосунку поведінка також залежить від кешування та способу розгортання.
Без меж Suspense повільний Server Component може затримувати відображення пов’язаного з ним контенту.
Коли межі розташовані окремо:
<Suspense fallback={<p>Завантаження статистики...</p>}>
<Statistics />
</Suspense>
<Suspense fallback={<p>Завантаження рекомендацій...</p>}>
<Recommendations />
</Suspense>Next.js може передати браузеру:
базову розмітку сторінки;
fallback для статистики;
готову статистику;
fallback для рекомендацій;
готові рекомендації.
Це називається потоковим відображенням — streaming. Сервер не чекає на завершення всіх частин сторінки, а надсилає їх у міру готовності.
Межу потрібно розташовувати навколо частини UI, яка може завантажуватися окремо.
<Suspense fallback={<p>Завантаження всієї сторінки...</p>}>
<Header />
<Statistics />
<Recommendations />
</Suspense>У такому випадку fallback замінює весь блок, включно з Header. Користувач побачить менше готового інтерфейсу, хоча заголовок міг би відображатися одразу.
<Header />
<Suspense fallback={<p>Завантаження статистики...</p>}>
<Statistics />
</Suspense>
<Suspense fallback={<p>Завантаження рекомендацій...</p>}>
<Recommendations />
</Suspense>Тут кожна повільна частина має власну межу, а готовий контент не приховується.
Розмір межі залежить від UI:
для невеликого віджета достатньо окремого fallback;
для пов’язаної групи компонентів можна використати одну спільну межу;
для незалежних секцій краще створити кілька меж.
Fallback — це не обов’язково текст Завантаження.... Він має відповідати формі майбутнього контенту.
Наприклад, для картки можна показати спрощений шаблон:
function CardFallback() {
return (
<article aria-busy="true">
<h2>Завантаження картки...</h2>
<p>Будь ласка, зачекайте.</p>
</article>
);
}Корисні властивості fallback:
він пояснює, яка частина UI завантажується;
займає приблизно стільки ж місця, скільки майбутній контент;
не змушує весь екран блимати;
доступний для користувачів допоміжних технологій.
aria-busy="true" повідомляє, що відповідна область ще оновлюється.
Сам факт обгортання компонента в Suspense не додає йому асинхронної поведінки:
<Suspense fallback={<p>Завантаження...</p>}>
<RegularComponent />
</Suspense>Якщо RegularComponent одразу повертає JSX і нічого не очікує, fallback не буде показаний.
Suspense має сенс, коли вкладений контент справді може призупинити рендеринг, наприклад через:
очікування в асинхронному Server Component;
підтримуваний React механізм читання асинхронних даних;
інший компонент або бібліотеку, які інтегровані з Suspense.
Suspense можна використовувати в дереві, де є Client Components, але він не замінює звичайний стан завантаження для useEffect.
Наприклад, такий компонент сам по собі не передає стан завантаження до Suspense:
"use client";
import { useEffect, useState } from "react";
export default function ClientData() {
const [data, setData] = useState<string | null>(null);
useEffect(() => {
fetch("/api/data")
.then((response) => response.json())
.then((result) => setData(result.value));
}, []);
if (!data) {
return <p>Завантаження...</p>;
}
return <p>{data}</p>;
}У цьому випадку компонент сам показує текст на основі useState. Suspense не перехоплює очікування, створене всередині useEffect.
Для асинхронних Server Components у Next.js Suspense часто є природним способом розділити сторінку на незалежні частини. Для Client Components потрібно перевіряти, чи використовувана бібліотека або підхід справді підтримує Suspense.
Межі можна вкладати, якщо різним рівням потрібні різні fallback:
<Suspense fallback={<p>Завантаження дашборда...</p>}>
<Dashboard>
<Suspense fallback={<p>Завантаження графіка...</p>}>
<Chart />
</Suspense>
</Dashboard>
</Suspense>У такій структурі:
зовнішній fallback покриває весь дашборд;
внутрішній fallback стосується лише графіка;
після готовності Dashboard його дочірні частини все ще можуть завантажуватися окремо.
Не варто створювати межі без потреби. Кожна межа повинна відповідати помітній самостійній частині інтерфейсу.
Suspense і loading.tsxУ Next.js можна створити файл loading.tsx для сегмента маршруту:
app/
├── dashboard/
│ ├── page.tsx
│ └── loading.tsxloading.tsx зручно використовувати як загальний fallback для маршруту. Suspense у самій сторінці потрібен тоді, коли треба керувати окремими частинами UI:
<Suspense fallback={<StatisticsFallback />}>
<Statistics />
</Suspense>Отже:
loading.tsx — fallback для маршруту або його сегмента;
Suspense — точковий контроль над окремою частиною сторінки.
Якщо одна повільна секція обгортає весь екран, користувач не побачить навіть швидкий контент.
Краще обмежити Suspense лише компонентом, який справді очікує дані.
Коли кілька секцій мають різну швидкість завантаження, один fallback створює враження, що весь контент готовий лише після завершення найповільнішої операції.
Для незалежних частин використовуйте окремі межі.
Текст «Завантаження...» без пояснення може бути незрозумілим. Краще вказати, що саме завантажується:
<Suspense fallback={<p>Завантажуємо список замовлень...</p>}>
<Orders />
</Suspense>useEffect автоматично активує SuspenseЗапит у useEffect не призупиняє рендеринг для батьківського Suspense. У такому випадку стан завантаження потрібно обробляти всередині Client Component.
Якщо компонент синхронний, fallback не з’явиться. Suspense не є універсальним способом показувати анімацію під час будь-якого рендерингу.
Suspense дозволяє показувати fallback для ще не готової частини UI.
У Next.js він особливо корисний для асинхронних Server Components.
Межі Suspense дають змогу поступово відображати сторінку через streaming.
Незалежні повільні секції варто обгортати в окремі межі.
Хороший fallback описує майбутній контент і займає схоже місце в інтерфейсі.
Suspense не замінює локальний стан завантаження в useEffect.
loading.tsx підходить для fallback усього сегмента маршруту, а Suspense — для точкового керування частинами сторінки.