Фронт и 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, ... } }.