JavaRush /Курсы /Модуль 4: Node.js, Next.js и Angular /fetch() в Server Components: базовые примеры

fetch() в Server Components: базовые примеры

Модуль 4: Node.js, Next.js и Angular
9 уровень , 0 лекция
Открыта

1. fetch() в Server Components

Давайте представим типичную задачу: вы хотите показать пользователю список новостей, товаров или пользователей, которые приходят из внешнего API. Раньше (в классическом React) вы бы использовали useEffect и загружали данные на клиенте, а страница сначала была бы пустой или показывала "Загрузка...". Но в Next.js 15 с App Router всё иначе: теперь данные можно получать прямо на сервере, ещё до того как HTML попадёт в браузер пользователя. Это ускоряет загрузку страницы, улучшает SEO и делает приложение более отзывчивым.

В Server Components вы можете вызывать функцию fetch() прямо в компоненте — синхронно или асинхронно, без всяких хуков! Давайте посмотрим, как это работает.

Как выглядит Server Component

Обычный Server Component — это просто React-компонент, который не содержит директивы "use client". Например, ваш файл app/page.tsx:


// app/page.tsx

export default function HomePage() {
  return <h1>Привет, Next.js 15!</h1>;
}

Теперь давайте попробуем получить данные с помощью fetch().

Пример: загрузка списка пользователей

Допустим, у нас есть публичное API, например JSONPlaceholder, которое отдаёт список пользователей в формате JSON. Попробуем вывести их на главной странице.


// app/page.tsx

// Обратите внимание: это Server Component (нет "use client")

export default async function HomePage() {
  // fetch() можно вызывать прямо здесь!
  const res = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await res.json();

  return (
    <main>
      <h1>Список пользователей</h1>
      <ul>
        {users.map((user: any) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>
    </main>
  );
}

Что здесь происходит?

  • Компонент объявлен как async — это разрешено в Server Components.
  • Мы вызываем fetch() с URL внешнего API.
  • Получаем ответ и парсим его как JSON.
  • Рендерим список пользователей.

Важный момент: этот код будет выполнен на сервере, а браузер получит уже готовый HTML с данными. Пользователь увидит сразу заполненную страницу, без моргания и "загрузок".

2. Особенности fetch() в Server Components

Можно ли использовать fetch() так же, как в браузере?

Да, но с некоторыми нюансами:

  • В Server Components функция fetch() работает на сервере, а не в браузере.
  • Можно использовать любые URL — как внешние (например, API), так и внутренние (ваши собственные API-роуты).
  • Можно (и нужно!) использовать await прямо внутри компонента.

fetch() автоматически кешируется

Next.js по умолчанию кеширует результат fetch() в Server Components. Это значит, что если вы несколько раз вызовете одинаковый запрос, Next.js не будет гонять одни и те же данные туда-сюда, а отдаст их из кеша.


const res1 = await fetch('https://jsonplaceholder.typicode.com/users');
const res2 = await fetch('https://jsonplaceholder.typicode.com/users');
// Второй запрос не пойдёт в сеть — данные будут взяты из кеша!

Это ускоряет работу приложения, но иногда может привести к "залипанию" устаревших данных. Управлять кешем мы научимся на следующих лекциях.

fetch() можно вызывать с разными опциями

Вы можете передавать дополнительные параметры в fetch(), как в браузере:


await fetch('https://example.com/api/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ foo: 'bar' })
});

Но чаще всего для получения данных в Server Components используют метод GET.

3. Практика: развиваем ваше приложение

Продолжим строить учебное приложение — теперь оно будет показывать список задач (todos) из публичного API.

Создаём страницу задач

Создайте файл app/todos/page.tsx:


// app/todos/page.tsx

export default async function TodosPage() {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos?_limit=10');
  const todos = await res.json();

  return (
    <main>
      <h1>Задачи</h1>
      <ul>
        {todos.map((todo: any) => (
          <li key={todo.id}>
            <input type="checkbox" checked={todo.completed} readOnly />
            {todo.title}
          </li>
        ))}
      </ul>
    </main>
  );
}

Что нового:

  • Мы ограничили количество задач до 10 с помощью параметра _limit=10.
  • Для каждой задачи отображаем чекбокс (отмечен, если задача выполнена).

Как это работает в Next.js 15

  • Когда пользователь заходит на /todos, Next.js вызывает ваш компонент на сервере.
  • Сервер делает запрос к API, получает данные и формирует HTML.
  • Пользователь получает уже готовую страницу со списком задач.

4. fetch() для внутренних API-роутов

Вы можете использовать fetch() не только для внешних API, но и для своих собственных серверных эндпоинтов.

Пример: запрос к своему Route Handler

Допустим, у вас есть такой файл:


// app/api/hello/route.ts

export async function GET() {
  return Response.json({ message: 'Привет из API!' });
}

Теперь можно получить этот ответ из Server Component:


// app/page.tsx

export default async function HomePage() {
  const res = await fetch('http://localhost:3000/api/hello');
  const data = await res.json();

  return <div>{data.message}</div>;
}

В реальности:
В продакшене используйте относительный путь (/api/hello), чтобы не зависеть от порта и протокола:


const res = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/hello`);

или просто:


const res = await fetch('http://localhost:3000/api/hello', { cache: 'no-store' });

NB: Внутренние fetch-запросы в Server Components автоматически обрабатываются оптимально — Next.js не делает лишних HTTP-запросов, а вызывает ваши обработчики напрямую.

5. Полезные нюансы

Особенности и ограничения

  • fetch() только на сервере
    Всё, что связано с fetch() в Server Components, выполняется на сервере. Это значит:
    • Нет доступа к браузерным API (например, localStorage).
    • Нельзя напрямую реагировать на действия пользователя (для этого нужны Client Components).
  • fetch() в Client Components
    В Client Components нельзя использовать асинхронные компоненты с await fetch() прямо в теле компонента. Для этого нужны хуки (useEffect, useState). Подробнее об этом — на следующих лекциях.
  • Ошибки при загрузке данных
    Если API не отвечает или возвращает ошибку, ваш компонент может "упасть". На следующих лекциях мы научимся обрабатывать ошибки и показывать fallback-состояния (например, "Ошибка загрузки данных").

fetch() и кеширование: базовое представление

В Server Components Next.js по умолчанию кеширует результаты fetch-запросов. Это значит, что если вы не укажете дополнительные опции, данные будут "залипать" до перезапуска сервера или пересборки страницы (если используется статическая генерация).

Если нужно отключить кеширование:


const res = await fetch('https://example.com/api/data', { cache: 'no-store' });

Если нужно явно указать кешировать:


const res = await fetch('https://example.com/api/data', { cache: 'force-cache' });

Подробнее о кешировании — на следующей лекции!

6. Типичные ошибки

Ошибка №1: попытка использовать браузерные API
В Server Components нельзя использовать window, document, localStorage и прочие браузерные фишки. fetch() здесь работает только для получения данных с сервера.

Ошибка №2: забыли сделать компонент async
Если вы пишете:


export default function HomePage() {
  const res = await fetch('...');
  // ...
}

— получите ошибку: нельзя использовать await вне async-функции. Не забудьте добавить async перед function!

Ошибка №3: забыли обработать ошибку ответа
Если API вернул ошибку (например, 404), а вы сразу делаете await res.json(), получите исключение. Лучше добавить проверку:


if (!res.ok) {
  // обработать ошибку
}

Ошибка №4: используете fetch() с абсолютным адресом для собственного API
Если вы пишете fetch('http://localhost:3000/api/hello'), такой код не будет работать в продакшене или на Vercel. Используйте относительный путь: fetch('/api/hello').

Ошибка №5: используете fetch() в Client Component как в Server Component
В Client Components нельзя просто так делать await fetch() в теле компонента. Для этого нужны хуки useEffect и useState.

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ