Loading и Error
Next.js предоставляет специальные файлы для состояний загрузки и ошибок.
loading.tsx
Показывается, пока страница грузится. Автоматически оборачивает в <Suspense>.
// app/posts/loading.tsx
export default function Loading() {
return (
<div className="skeleton">
<div className="skeleton-line" />
<div className="skeleton-line" />
<div className="skeleton-line" />
</div>
);
}
Работает для всех страниц в папке.
error.tsx
Ловит ошибки рендера и в дочерних компонентах.
'use client';
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Что-то пошло не так</h2>
<p>{error.message}</p>
<button onClick={reset}>Попробовать снова</button>
</div>
);
}
Должен быть клиентским компонентом.
reset() — попытка перерендерить сегмент.
global-error.tsx
Ловит ошибки в root layout:
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<html>
<body>
<h2>Критическая ошибка</h2>
<button onClick={reset}>Перезагрузить</button>
</body>
</html>
);
}
Должен содержать <html> и <body>.
not-found.tsx
Показывается при notFound() или 404:
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h1>404</h1>
<p>Страница не найдена</p>
<Link href="/">На главную</Link>
</div>
);
}
Вложенный:
// app/posts/[id]/not-found.tsx
export default function PostNotFound() {
return <p>Пост не найден</p>;
}
Показывается для 404 внутри /posts/[id].
notFound()
import { notFound } from 'next/navigation';
const post = await getPost(id);
if (!post) notFound();
Показывает ближайший not-found.tsx.
Иерархия
app/
├── layout.tsx
├── loading.tsx ← для /
├── error.tsx ← для /
├── not-found.tsx ← для /
├── posts/
│ ├── loading.tsx ← для /posts
│ ├── error.tsx ← для /posts
│ └── [id]/
│ ├── loading.tsx ← для /posts/[id]
│ └── not-found.tsx ← для /posts/[id]
Каждый уровень может иметь свои.
Suspense
Более тонкий контроль:
import { Suspense } from 'react';
export default function Page() {
return (
<>
<h1>Заголовок</h1>
<Suspense fallback={<Skeleton />}>
<SlowComponent />
</Suspense>
<Suspense fallback={<p>Загрузка комментариев...</p>}>
<Comments />
</Suspense>
</>
);
}
Каждый блок грузится отдельно.
Streaming
Next.js стримит HTML по мере готовности. Пользователь видит части страницы до полной загрузки.
export default function Page() {
return (
<>
<Header /> {/* сразу */}
<Suspense fallback={<Spinner />}>
<SlowData /> {/* по мере готовности */}
</Suspense>
</>
);
}
Обработка ошибок в Server Actions
'use server';
export async function createPost(prevState, formData: FormData) {
try {
await db.post.create({ data: { title: formData.get('title') } });
revalidatePath('/posts');
return { success: true };
} catch (error) {
return { error: 'Не удалось создать' };
}
}
Ошибки возвращаются как состояние формы, а не через error.tsx.
Логирование ошибок
'use client';
import { useEffect } from 'react';
export default function Error({ error, reset }) {
useEffect(() => {
console.error('Ошибка:', error);
// отправить в Sentry
}, [error]);
return (
<div>
<h2>Ошибка</h2>
<button onClick={reset}>Повторить</button>
</div>
);
}
Итоги
loading.tsx— состояние загрузкиerror.tsx— обработка ошибокglobal-error.tsx— для root layoutnot-found.tsx— 404notFound()для ручного вызоваSuspenseдля тонкого контроля- Streaming показывает части страницы