Загрузка данных в серверных компонентах
В курсе React данные загружались в useEffect уже в браузере: сначала пустая страница, потом запрос, потом данные. В серверном компоненте всё проще — данные получают через await прямо в теле компонента, и в браузер приходит страница, где они уже есть.
Данные из своих функций
Самый частый случай — вызвать функцию, которая читает базу данных. В блоге это getPosts() из lib/posts.ts:
// app/page.tsx
import Link from 'next/link';
import { getPosts } from '@/lib/posts';
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>Мой блог</h1>
{posts.map((post) => (
<p key={post.slug}>
<Link href={`/posts/${post.slug}`}>{post.title}</Link>
</p>
))}
</main>
);
}Главная страница показывает список статей. Откройте исходный код страницы в браузере (Ctrl+U) — заголовки статей уже есть в HTML.
Данные из внешнего API
Для запроса к чужому сервису используют обычный fetch. Возьмём учебный сервис JSONPlaceholder:
// app/external/page.tsx
type ExternalPost = { id: number; title: string };
export default async function ExternalPage() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=3');
const posts: ExternalPost[] = await response.json();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}По адресу /external появятся три заголовка. Запрос выполняет сервер, поэтому ограничения CORS здесь не мешают, а ключ API, если он нужен, не попадёт в браузер.
Несколько запросов параллельно
Два await подряд ждут друг друга. Если запросы независимы, запустите их одновременно через Promise.all:
// app/posts/[slug]/page.tsx (фрагмент)
const [post, comments] = await Promise.all([getPost(slug), getComments(slug)]);Статья и комментарии загружаются одновременно, и страница готова быстрее. Функцию getComments добавим вместе с базой данных.
Обработка ошибки
Если внешний сервис ответил ошибкой, проверьте response.ok и выбросите исключение:
// app/external/page.tsx (фрагмент)
if (!response.ok) {
throw new Error('Не удалось загрузить статьи');
}Исключение покажет ближайший файл error.tsx — о нём урок об обработке ошибок.
А в клиентском компоненте?
Клиентский компонент не может быть async. Данные для него обычно загружают в серверном родителе и передают через свойства. Для данных, которые меняются прямо в браузере, по-прежнему подходят useEffect или библиотеки вроде SWR и TanStack Query.
Частая ошибка
Делать запрос из серверного компонента к собственному API того же приложения: fetch('/api/posts'). На сервере относительный адрес не работает, а сам лишний HTTP-запрос не нужен. Вызывайте ту же функцию, что использует API, напрямую: await getPosts().