Загрузка и потоковая отрисовка
Пока серверный компонент ждёт данные, посетитель видит пустой экран. Чтобы этого не было, Next.js умеет отправлять страницу частями: сначала то, что готово, и заглушку на месте медленной части, затем — недостающий кусок. Такой способ называют потоковой отрисовкой (streaming).
Медленные данные
Для опыта сделаем компонент комментариев, который загружается две секунды. Создайте components/Comments.tsx:
// components/Comments.tsx
export default async function Comments() {
await new Promise((resolve) => setTimeout(resolve, 2000));
const comments = ['Олег: Отличная статья!', 'Анна: Спасибо!'];
return (
<ul>
{comments.map((text) => (
<li key={text}>{text}</li>
))}
</ul>
);
}Задержка имитирует медленный запрос к базе. Если вставить <Comments /> в страницу статьи как обычно, вся страница будет открываться две секунды.
Файл loading.tsx
Самый простой способ — положить рядом со страницей файл loading.tsx:
// app/posts/[slug]/loading.tsx
export default function Loading() {
return <p>Загружаем статью...</p>;
}Пока страница статьи готовится, на её месте виден текст «Загружаем статью...». Макеты — шапка и меню — показываются сразу, заглушка заменяет только содержимое страницы.
Suspense для части страницы
Файл loading.tsx заменяет всю страницу. Чтобы статья появилась сразу, а ждали только комментарии, оберните медленный компонент в Suspense из React:
// app/posts/[slug]/page.tsx
import { Suspense } from 'react';
import { notFound } from 'next/navigation';
import Comments from '@/components/Comments';
import { getPost } from '@/lib/posts';
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<Suspense fallback={<p>Загружаем комментарии...</p>}>
<Comments />
</Suspense>
</article>
);
}Откройте /posts/hello-nextjs: статья видна сразу, под ней — «Загружаем комментарии...», а через две секунды на этом месте появится список.
Как это устроено
| Способ | Что заменяет заглушка | Где пишется |
|---|---|---|
loading.tsx | Всю страницу папки | Файл рядом с page.tsx |
Suspense | Только обёрнутую часть | Внутри разметки |
Свойство fallback — то, что показать вместо содержимого, пока оно не готово. На одной странице может быть несколько Suspense, каждый появляется независимо.
Частая ошибка
Загрузить медленные данные в самой странице через await, а потом передать их в компонент внутри Suspense. Страница всё равно будет ждать, ведь await стоит выше. Медленный запрос должен выполняться внутри компонента, который обёрнут в Suspense, как Comments в примере.