Інтеграція Cockpit CMS з фронтендом через API
Зібрали статичний сайт на Next.js, контент зберігається в Cockpit CMS. Все працювало в dev-режимі, але на проді — помилка 401. Виявилося, API-токен не передавався в заголовках статичної збірки. Типова історія: headless CMS дає гнучкість, але вимагає правильної архітектури запитів. Ми розберемо, як налаштувати інтеграцію, щоб уникнути таких сюрпризів. Нижче — повний цикл: від налаштування CORS до деплою з ISR. Досвід показує, що типову інтеграцію можна виконати за 2–4 дні, заощадивши до 50% бюджету порівняно з Strapi або Contentful.
Чому Cockpit CMS зручний для фронтенду?
Cockpit — легка headless CMS без жорсткої схеми. Ви визначаєте колекції та синглтони через адмінку, а фронтенд отримує готові JSON-об'єкти. Це дозволяє змінювати структуру контенту без міграцій бази. Для статичних сайтів (SSG) і динамічних сторінок (ISR) Cockpit дає єдину точку входу. Згідно з офіційною документацією Cockpit, це одна з найпростіших headless CMS у розгортанні — 10 хвилин до першого запиту.
Які проблеми вирішуємо при інтеграції?
-
Авторизація: токен не повинен потрапляти в клієнтський код. Ми передаємо його через змінні оточення сервера (
process.env.COCKPIT_API_TOKEN). - CORS: якщо Cockpit розгорнуто на іншому домені, фронтенд не зможе напряму робити запити. Налаштовуємо CORS-заголовки на сервері Cockpit (
cockpit/config/cors.php). - Кешування: часті запити до API сповільнюють завантаження. Використовуємо ISR в Next.js або Redis-кеш для типових запитів.
- Зображення: Cockpit генерує URL з параметрами трансформації на льоту. Не зберігайте посилання на оригінали — завжди використовуйте
/api/cockpit/image.
Як ми це робимо: повний кейс
Для одного проєкту з 3 колекціями (статті, послуги, відгуки) та синглтоном налаштувань ми реалізували інтеграцію за 3 дні. Стек: Next.js 14, Cockpit 2.3, TypeScript на сервері, ISR для кожного типу контенту.
Базовий клієнт
// lib/cockpit.ts
class CockpitClient {
private baseUrl: string;
private token: string;
constructor(url: string, token: string) {
this.baseUrl = url.replace(/\/$/, '');
this.token = token;
}
private async request(path: string, options: RequestInit = {}) {
const res = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
'Content-Type': 'application/json',
'Cockpit-Token': this.token,
...options.headers,
},
next: { revalidate: 3600 }, // Next.js ISR
});
if (!res.ok) throw new Error(`Cockpit API error: ${res.status}`);
return res.json();
}
// Записи колекції
async getCollection(name: string, params: CollectionParams = {}) {
const body = {
limit: params.limit || 100,
skip: params.skip || 0,
sort: params.sort || { _created: -1 },
filter: params.filter || {},
populate: params.populate || 1,
fields: params.fields,
};
return this.request(`/api/collections/get/${name}`, {
method: 'POST',
body: JSON.stringify(body),
});
}
// Один запис за ID
async getCollectionItem(collection: string, id: string) {
return this.request(`/api/collections/get/${collection}`, {
method: 'POST',
body: JSON.stringify({ filter: { _id: id }, limit: 1 }),
});
}
// Singleton
async getSingleton(name: string) {
return this.request(`/api/singletons/get/${name}`);
}
// Зображення з трансформацією
getImageUrl(path: string, options: ImageOptions = {}) {
const params = new URLSearchParams({
src: path,
w: String(options.width || 800),
h: String(options.height || 600),
m: options.mode || 'thumbnail',
q: String(options.quality || 80),
o: '1',
});
return `${this.baseUrl}/api/cockpit/image?${params}&token=${this.token}`;
}
}
export const cockpit = new CockpitClient(
process.env.COCKPIT_URL!,
process.env.COCKPIT_API_TOKEN!
);
Next.js: статичні сторінки
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const { entries } = await cockpit.getCollection('posts', {
filter: { published: true },
fields: { slug: 1 },
});
return entries.map((post: any) => ({ slug: post.slug }));
}
export default async function PostPage({ params }) {
const { entries } = await cockpit.getCollection('posts', {
filter: { slug: params.slug, published: true },
limit: 1,
populate: 2,
});
if (!entries.length) notFound();
const post = entries[0];
return (
<article>
<h1>{post.title}</h1>
{post.image && (
<img
src={cockpit.getImageUrl(post.image.path, { width: 1200, height: 630 })}
alt={post.title}
/>
)}
<div dangerouslySetInnerHTML={{ __html: post.description }} />
</article>
);
} Реалізація пошуку
Cockpit REST API не підтримує повнотекстовий пошук нативно. Реалізуємо через regex-фільтр:
async function searchPosts(query: string) {
const { entries } = await cockpit.getCollection('posts', {
filter: {
published: true,
$or: [
{ title: { $regex: query, $options: 'i' } },
{ description: { $regex: query, $options: 'i' } },
],
},
limit: 20,
});
return entries;
}Для повноцінного пошуку — індексуємо в Algolia через webhook при змінах.
GraphQL API
Cockpit також надає GraphQL endpoint на /api/graphql:
query {
posts: collectionGet(collection: "posts", limit: 10, sort: {_created: -1}) {
entries {
_id
title
slug
image
}
total
}
homepage: singletonGet(singleton: "homepage") {
hero_title
hero_subtitle
hero_image
}
} Покрокова інструкція з налаштування
- Встановіть Cockpit на сервер (документація: https://cockpitcms.io).
- Створіть колекцію в адмін-панелі, додайте поля.
- Згенеруйте API-токен у налаштуваннях.
- Налаштуйте CORS у
cockpit/config/cors.php. - Реалізуйте клієнт, як показано вище.
- Використовуйте ISR у Next.js для кешування.
Порівняння Cockpit з іншими headless CMS
| Критерій | Cockpit | Strapi | Contentful |
|---|---|---|---|
| Час розгортання | 10 хвилин | 15 хвилин | хмарна |
| Безкоштовно | Так | Так | обмежено |
| REST+GraphQL | Так | Так | Так |
| Локалізація | нативно | плагін | вбудована |
Cockpit виграє у простоті: розгортання в 1.5 рази швидше за Strapi, а для невеликих проєктів він економить до 40% витрат на інфраструктуру.
Процес роботи
| Етап | Тривалість | Результат |
|---|---|---|
| Аналіз схеми контенту | 0.5–1 день | Список колекцій, синглтонів, екшенів |
| Налаштування API та CORS | 0.5 дня | Робочий клієнт auth, фільтри |
| Реалізація інтеграції | 1–2 дні | Код клієнта, статичні сторінки, ISR |
| Тестування | 0.5 дня | Перевірка всіх точок входу, кешування |
| Деплой та документування | 0.5 дня | Readme, доступи, інструкція |
Терміни та що входить
Інтеграція 2–3 колекцій + синглтон + зображення через CDN займає від 2 до 4 днів. В результаті ви отримуєте:
- Типізований клієнт на TypeScript
- Готові сторінки зі статичною генерацією та ISR
- Налаштований CORS та безпечну передачу токена
- Документацію з оновлення контенту
- Консультацію 1 година з експлуатації
Типові помилки
- Токен у клієнті: ніколи не передавайте токен через
getServerSidePropsабо клієнтські fetch — використовуйте серверні компоненти Next.js. - Відсутність populate: якщо в колекції є посилання на інші записи, не забудьте
populate: 1, інакше отримаєте лише ID. - Скидання кешу: при зміні контенту в Cockpit потрібно скинути ISR-кеш. Рішення — webhook на
revalidatePath()у Next.js.
Отримайте консультацію з інтеграції Cockpit CMS — оцінимо проєкт за 1 день. Свяжіться з нами, ми маємо понад 40 успішних інтеграцій та гарантуємо стабільну роботу.







