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.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ