Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Загрузка данных в серверных компонентах

В курсе 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().

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.