Фронт и API: подключение и примеры

Переменные окружения

На фронте (Next.js, React и т.д.) укажите URL API. Админка использует NEXT_PUBLIC_API_URL. Сайт, который только читает контент, может использовать NEXT_PUBLIC_CMS_API_URL или тот же NEXT_PUBLIC_API_URL.

# .env.local или переменные при сборке
NEXT_PUBLIC_API_URL=https://api.yoursite.com
# Для сайта, запрашивающего контент, часто используют:
NEXT_PUBLIC_CMS_API_URL=https://api.yoursite.com

На проде укажите полный HTTPS-адрес API (например https://api.pxlr.ru). CORS на API должен разрешать ваш домен сайта.

Публичные эндпоинты (без авторизации)

Для отображения контента на сайте обычно не нужен JWT. Достаточно GET-запросов:

  • GET /content?schemaName=...&status=published&locale=...&limit=... — список документов.
  • GET /content/:id — один документ по ID.
  • GET /navigation?locale=ru — пункты меню.
  • GET /settings/seo — настройки сайта.
  • GET /schemas — список схем.
  • GET /media — список медиа (при необходимости).

Получение списка документов

Запрос с параметрами: schemaName, status=published, locale, page, limit, orderBy, order, search.

const API_URL = process.env.NEXT_PUBLIC_API_URL || process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:4000';

// Список документов по схеме (опубликованные, локаль)
const res = await fetch(
  `${API_URL}/content?schemaName=page&status=published&locale=ru&limit=50`,
  { next: { revalidate: 60 } } // Next.js: кэш 60 сек
);
const data = await res.json();
const documents = data.documents || [];

Получение одного документа по ID

// Один документ по ID
const res = await fetch(`${API_URL}/content/${documentId}`, {
  next: { revalidate: 60 },
});
const data = await res.json();
const document = data.document;

Страница по slug

API не отдаёт документ по slug напрямую — нужно запросить список и найти элемент с data.slug === slug. Пример для маршрута страницы:

// Страница по slug (например для маршрута /[slug])
async function getPage(slug: string, locale = 'ru') {
  const res = await fetch(
    `${API_URL}/content?schemaName=page&status=published&locale=${locale}&limit=50`,
    { next: { revalidate: 60 } }
  );
  if (!res.ok) return null;
  const data = await res.json();
  return data.documents?.find((d: any) => d.data?.slug === slug) ?? null;
}

Навигация (меню)

// Меню сайта для шапки
const res = await fetch(`${API_URL}/navigation?locale=${locale}`);
const data = await res.json();
const items = data.items || [];
// item: { id, label, url, target }

Настройки сайта (SEO)

// Настройки SEO/сайта
const res = await fetch(`${API_URL}/settings/seo`, {
  next: { revalidate: 300 },
});
const settings = await res.json();
// primaryLanguage, multiLanguageEnabled, siteName, siteDescription, homepageId, ...

Медиа и схемы

// Список медиа (для галерей и т.д.)
const res = await fetch(
  `${API_URL}/media?folder=/&limit=20`,
  { next: { revalidate: 60 } }
);
const data = await res.json();
const files = data.files || [];
// Список схем (типы контента)
const res = await fetch(`${API_URL}/schemas`);
const data = await res.json();
const schemas = data.schemas || [];

Модуль-помощник (lib/cms)

Удобно вынести запросы в один модуль и переиспользовать в страницах и компонентах:

// app/lib/cms.ts — пример модуля для сайта
const API_URL = process.env.NEXT_PUBLIC_CMS_API_URL || 'http://localhost:4000';

export async function getBlogPosts(locale = 'ru') {
  const res = await fetch(
    `${API_URL}/content?schemaName=blog&status=published&locale=${locale}&limit=50`,
    { next: { revalidate: 60 } }
  );
  const data = await res.json();
  return data.documents || [];
}

export async function getBlogPostBySlug(slug: string, locale = 'ru') {
  const posts = await getBlogPosts(locale);
  return posts.find((p: any) => p.data?.slug === slug) ?? null;
}

Клиентские запросы (useEffect)

В клиентском React-компоненте запросы выполняйте в useEffect и сохраняйте результат в state:

// В клиентском компоненте ('use client')
const [items, setItems] = useState([]);
useEffect(() => {
  fetch(`${process.env.NEXT_PUBLIC_API_URL}/content?schemaName=blog&status=published&limit=10`)
    .then((r) => r.json())
    .then((data) => setItems(data.documents || []));
}, []);

Изображения из API

В полях типа image API возвращает объект с url (и опционально alt). Если MinIO доступен по внутреннему имени (minio:9000), замените хост на публичный URL при отображении на сайте:

// URL изображения из поля image документа
// В данных обычно: data.image = { url: "https://...", alt: "..." }
// При локальном MinIO замените хост на публичный:
const imageUrl = (doc.data?.image?.url || '')
  .replace('http://minio:9000', process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000');

Структура ответа /content

Список: { documents: [...], pagination: { page, limit, total, totalPages } }. Каждый элемент: id, schema_name, locale, status, data (объект с полями из схемы), created_at, updated_at. Один документ: { document: { id, schema_name, data, ... } }.