К содержанию

HTTP-запрос и проверка ответа API

Получаем данные по сети, проверяем их через Zod и показываем результат или ошибку.

На этой странице

Для Next.js 16.3.8 (App Router) и React 19.3.0, Zod 4.6.5. Обновлено .

В Next-02 афиша получала подготовленный массив, а функция readJson показывала HTTP-запрос отдельно. Теперь соединим запрос, проверку неизвестных данных и интерфейс. Для небольшого самостоятельного примера добавим страницу /news с учебными публикациями. Каталог /events и форму менять не нужно.

Используем JSONPlaceholder  – учебный API с вымышленными данными. Его /posts возвращает публикации с id, title и body. Для GET-запроса не нужны регистрация и ключ. Это внешний сервис: без доступа к сети пример покажет ошибку.

Zod уже установлен на этапе форм. Сохраните lib/read-json.ts из статьи о получении данных. Следующие блоки – полные файлы, а не фрагменты для вставки внутрь существующего компонента.

Получение и проверка

lib/news.ts
import { z } from "zod";
import { readJson } from "./read-json";
 
export const newsSchema = z.array(
  z.object({
    id: z.number().int().positive(),
    title: z.string().min(1),
    body: z.string(),
  }),
);
 
export type NewsItem = z.infer<typeof newsSchema>[number];
 
export async function loadNews(): Promise<NewsItem[]> {
  const data: unknown = await readJson(
    "https://jsonplaceholder.typicode.com/posts",
  );
  const result = newsSchema.safeParse(data);
 
  if (!result.success) {
    throw new Error("Источник вернул публикации неизвестного формата");
  }
 
  return result.data;
}

Здесь две отдельные проверки. readJson проверяет HTTP-статус и читает JSON. newsSchema проверяет устройство полученного значения: массив ли это, есть ли нужные поля и подходят ли их типы. Ответ 200 с правильным JSON ещё не означает, что данные подходят приложению.

Схема выбирает только используемые поля; дополнительные свойства ответа не нужны для нашей страницы. Тип NewsItem получен из этой же схемы. Запись as NewsItem[] вместо проверки скрыла бы проблему от TypeScript, но не исправила бы данные.

Серверная страница

app/news/page.tsx
import { loadNews } from "@/lib/news";
 
export default async function NewsPage() {
  const items = await loadNews();
 
  return (
    <main className="mx-auto max-w-3xl p-6">
      <h1 className="mb-6 text-2xl font-semibold">Учебные публикации</h1>
      {items.length === 0 ? (
        <p>Публикаций пока нет.</p>
      ) : (
        <ul className="grid gap-4">
          {items.map((item) => (
            <li key={item.id}>
              <article className="border-border rounded-xl border p-4 break-words">
                <h2 className="mb-2 text-lg font-semibold">{item.title}</h2>
                <p>{item.body}</p>
              </article>
            </li>
          ))}
        </ul>
      )}
    </main>
  );
}

Это серверный компонент: он дожидается проверенных данных и передаёт их в разметку. Не добавляйте сюда "use client". Обычный текст ответа выводится через JSX, без вставки полученного HTML.

Добавьте в существующую навигацию <Link href="/news">Публикации</Link>. Адрес /news также можно открыть напрямую.

Ожидание и ошибка

app/news/loading.tsx
export default function Loading() {
  return <p role="status">Загружаем публикации…</p>;
}
app/news/error.tsx
"use client";
 
export default function ErrorPage({ retry }: { retry: () => void }) {
  return (
    <section role="alert">
      <h2>Не удалось загрузить публикации</h2>
      <p>Проверьте подключение и попробуйте ещё раз.</p>
      <button type="button" onClick={() => retry()}>
        Повторить
      </button>
    </section>
  );
}

Сбой сети, ошибочный HTTP-статус, неверный JSON и несовпадение со схемой приводят к сообщению о сбое. Пустой проверенный массив – успешный ответ без публикаций. Не подменяйте ошибку пустым массивом: иначе пользователь не отличит отсутствие данных от недоступности источника.

Проверьте разные ответы

Откройте /news, затем временно меняйте только получение data внутри loadNews:

СлучайЧто изменитьОжидаемый результат
Правильный ответОставить HTTP-запросПубликации из API
Пустой списокВместо запроса написать const data: unknown = [];«Публикаций пока нет»
Неправильная структураНаписать const data: unknown = [{ id: 1, title: null }];Интерфейс ошибки
HTTP-ошибкаВ адресе запроса заменить /posts на /missing-resourceИнтерфейс ошибки

Это временные способы разобрать поведение, а не настройка рабочего источника. В режиме разработки Next.js может дополнительно открыть окно диагностики. После проверки восстановите HTTP-запрос и правильный адрес. Нажатие «Повторить» снова обращается к источнику; оно не исправляет неверный адрес или схему само по себе.

Краткая загрузка не обязана быть заметной при каждом переходе. Для её разбора можно временно добавить учебную задержку перед запросом, как в Next-02, а затем удалить её. Замедление сети в DevTools влияет на браузер, но не замедляет запрос от сервера Next.js к внешнему API. Подробнее – в статье о состояниях страницы.

Такую же последовательность применяют к настоящей афише: получают ответ, проверяют его по схеме своей модели и только затем передают данные карточкам. Подготовленный массив /events при этом остаётся полезным для локальных упражнений.

Документация: получение данных Next.js , Zod , JSONPlaceholder .