Пошук уроків, статей та іншого контенту
Додасте стани завантаження й помилок для маршрутів і компонентів із використанням можливостей Next.js.
Під час переходу між сторінками або виконання асинхронних операцій користувач може чекати на дані. Якщо в цей момент не показати інтерфейс завантаження, сторінка може здаватися несправною.
Так само запит до сервера може завершитися помилкою. Замість порожньої сторінки або технічної помилки користувач має побачити зрозуміле повідомлення та, за можливості, кнопку повторної спроби.
У Next.js App Router для цього використовують спеціальні файли:
loading.js — стан завантаження сегмента маршруту;
error.js — обробник помилок сегмента маршруту;
Suspense — локальний стан завантаження окремого компонента.
Сегмент маршруту — це папка всередині app, наприклад app/dashboard.
loading.jsФайл loading.js автоматично використовується Next.js під час завантаження відповідного маршруту.
Структура:
app/
└── dashboard/
├── page.js
└── loading.jsВміст loading.js:
export default function Loading() {
return (
<main>
<h1>Панель керування</h1>
<p>Завантаження даних...</p>
</main>
);
}Під час переходу на /dashboard Next.js може показати цей компонент ще до того, як сервер завершить виконання page.js.
app/dashboard/loading.js відповідає за маршрут /dashboard і його дочірні маршрути.
Наприклад:
app/
└── dashboard/
├── loading.js
├── page.js
└── settings/
└── page.jsУ цьому випадку loading.js також може використовуватися під час завантаження /dashboard/settings.
Щоб створити окремий стан для дочірнього маршруту, додайте власний файл:
app/
└── dashboard/
└── settings/
├── page.js
└── loading.jsSuspenseloading.js показує стан завантаження всього сегмента маршруту. Якщо потрібно завантажувати частини сторінки незалежно, використовуйте Suspense.
import { Suspense } from "react";
async function Statistics() {
await new Promise((resolve) => setTimeout(resolve, 1500));
return <p>Статистика завантажена.</p>;
}
export default function DashboardPage() {
return (
<main>
<h1>Панель керування</h1>
<Suspense fallback={<p>Завантаження статистики...</p>}>
<Statistics />
</Suspense>
</main>
);
}У цьому прикладі:
сторінка може показати заголовок одразу;
Statistics завантажується асинхронно;
поки компонент не готовий, відображається fallback;
після завершення операції fallback замінюється результатом компонента.
Suspense зручний, коли сторінка складається з кількох незалежних блоків. Кожен блок може мати власний стан завантаження.
error.jsФайл error.js створює Error Boundary для сегмента маршруту.
app/
└── dashboard/
├── page.js
├── loading.js
└── error.jsКомпонент помилки повинен бути клієнтським, тому файл починається з директиви "use client":
"use client";
export default function Error({ error, reset }) {
return (
<main>
<h1>Не вдалося завантажити панель</h1>
<p>Спробуйте повторити операцію.</p>
<button type="button" onClick={() => reset()}>
Повторити
</button>
</main>
);
}Next.js передає компоненту два основні значення:
error — об'єкт помилки;
reset — функція, яка повторно намагається відрендерити сегмент маршруту.
Не варто показувати користувачу error.message, якщо він може містити службову або конфіденційну інформацію. Технічні деталі краще записувати в журнали на сервері або використовувати для внутрішнього моніторингу.
Нижче наведено приклад маршруту /dashboard, який має:
стан завантаження маршруту;
локальний стан завантаження компонента;
обробник помилки;
кнопку повторної спроби.
app/
└── dashboard/
├── error.js
├── loading.js
└── page.jsapp/dashboard/loading.jsexport default function Loading() {
return (
<main>
<h1>Панель керування</h1>
<p>Завантаження сторінки...</p>
</main>
);
}app/dashboard/error.js"use client";
export default function Error({ reset }) {
return (
<main>
<h1>Сталася помилка</h1>
<p>Дані панелі не вдалося завантажити.</p>
<button type="button" onClick={() => reset()}>
Спробувати ще раз
</button>
</main>
);
}app/dashboard/page.jsimport { Suspense } from "react";
async function loadReports() {
// Імітуємо повільний запит до сервера
await new Promise((resolve) => setTimeout(resolve, 1500));
// Для перевірки помилки запустіть застосунок із SIMULATE_ERROR=true
if (process.env.SIMULATE_ERROR === "true") {
throw new Error("Не вдалося завантажити звіти");
}
return [
{ id: 1, title: "Продажі за січень", status: "Готовий" },
{ id: 2, title: "Активність користувачів", status: "В обробці" },
];
}
async function Reports() {
const reports = await loadReports();
return (
<section>
<h2>Звіти</h2>
<ul>
{reports.map((report) => (
<li key={report.id}>
{report.title}: {report.status}
</li>
))}
</ul>
</section>
);
}
export default function DashboardPage() {
return (
<main>
<h1>Панель керування</h1>
<Suspense fallback={<p>Завантаження звітів...</p>}>
<Reports />
</Suspense>
</main>
);
}Запустіть застосунок у звичайному режимі:
npm run devПісля відкриття /dashboard спочатку відображатиметься повідомлення про завантаження звітів, а потім список звітів.
Щоб перевірити стан помилки, запустіть застосунок із відповідною змінною середовища:
SIMULATE_ERROR=true npm run devУ Windows PowerShell:
$env:SIMULATE_ERROR="true"; npm run devКоли loadReports викине помилку, Next.js покаже app/dashboard/error.js.
error.jsОбробник помилок діє для свого сегмента та його дочірніх сегментів.
app/
├── error.js
└── dashboard/
├── error.js
└── page.jsЯкщо помилка виникла в dashboard/page.js, спочатку буде використано dashboard/error.js.
Це дає змогу створювати різні повідомлення для різних частин застосунку:
загальний обробник для всього застосунку;
спеціальний обробник для панелі керування;
ще точніший обробник для вкладеного маршруту.
Компонент error.js не замінює батьківський layout.js, тому помилки в самому layout можуть вимагати обробника на вищому рівні.
resetФункція reset повторно рендерить сегмент, у якому сталася помилка:
"use client";
export default function Error({ reset }) {
return (
<section>
<p>Не вдалося отримати дані.</p>
<button type="button" onClick={reset}>
Повторити
</button>
</section>
);
}Це корисно для тимчасових проблем:
короткочасна недоступність API;
мережевий збій;
помилка сервера, яка вже могла зникнути;
повторне завантаження даних після дії користувача.
Якщо причина помилки не змінилася, повторний рендер знову завершиться помилкою. У такому разі користувач і надалі бачитиме error.js.
error.js обробляє помилки під час рендерингу сегмента маршруту. Він не замінює перевірку помилок у звичайних обробниках подій.
Наприклад, помилку в onClick потрібно обробляти самостійно:
"use client";
import { useState } from "react";
export default function SaveButton() {
const [errorMessage, setErrorMessage] = useState("");
async function handleSave() {
setErrorMessage("");
try {
const response = await fetch("/api/settings", {
method: "POST",
});
if (!response.ok) {
throw new Error("Не вдалося зберегти налаштування");
}
} catch {
setErrorMessage("Налаштування не збережено. Спробуйте ще раз.");
}
}
return (
<div>
<button type="button" onClick={handleSave}>
Зберегти
</button>
{errorMessage && <p role="alert">{errorMessage}</p>}
</div>
);
}Тут try...catch потрібен тому, що помилка виникає всередині функції, викликаної подією користувача, а не під час рендерингу маршруту.
loading.jsпоказуйте структуру майбутнього контенту, якщо це можливо;
використовуйте короткі зрозумілі повідомлення;
для великих сторінок розділяйте завантаження за допомогою Suspense;
не показуйте нескінченний індикатор без пояснення або альтернативної дії.
error.jsдодавайте зрозуміле повідомлення для користувача;
надавайте кнопку повторної спроби через reset;
не показуйте стек помилки та внутрішні дані;
зберігайте технічну інформацію в журналах окремо від UI;
використовуйте "use client" на першому рядку файлу.
"use client"Неправильно:
export default function Error({ reset }) {
return <button onClick={reset}>Повторити</button>;
}Правильно:
"use client";
export default function Error({ reset }) {
return <button onClick={reset}>Повторити</button>;
}error.js використовує клієнтську взаємодію, тому має бути Client Component.
Якщо error.js розташований занадто високо або занадто низько в дереві app, він може не відповідати потрібному маршруту. Перевірте, у якій папці знаходиться файл, і який сегмент він охоплює.
error.js обробить помилку в onClickПомилки в обробниках подій потрібно ловити за допомогою try...catch і показувати через стан компонента.
Неправильно:
<p>{error.message}</p>Повідомлення може містити внутрішні деталі сервера. Краще показати загальний текст:
<p>Не вдалося завантажити дані. Спробуйте ще раз.</p>Один індикатор для всієї сторінки може приховувати вже готовий контент. Якщо частини сторінки завантажуються незалежно, використовуйте окремі межі Suspense.
loading.js автоматично показує стан завантаження сегмента маршруту.
Suspense дає змогу створити локальний fallback для окремого компонента.
error.js ловить помилки під час рендерингу сегмента та його дочірніх маршрутів.
Компонент error.js повинен містити "use client".
reset() повторно запускає рендеринг сегмента після помилки.
Помилки в обробниках подій потрібно обробляти вручну через try...catch.
Користувачу слід показувати безпечні та зрозумілі повідомлення, а не внутрішні деталі помилки.