Інтеграція 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 успішних інтеграцій та гарантуємо стабільну роботу.







