Інтеграція KeystoneJS для управління контентом

Ручна розробка GraphQL-сервера забирає десятки годин на рутинні операції, сповільнюючи запуск нових типів контенту. Ми інтегруємо KeystoneJS, автоматизуючи генерацію API та адмін-панелі з вашої моделі даних. Наша команда реалізує проєкт під ключ — від аудиту до підтримки, забезпечуючи надійне рішення, яке масштабується разом із бізнесом.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Інтеграція KeystoneJS для управління контентом
Середній
~3-5 днів
Часті запитання

Ручна розробка GraphQL-сервера — це десятки годин на написання резолверів, обробку помилок, аутентифікацію та пагінацію. Кожен новий тип контенту вимагає нового файлу з мутаціями та запитам. KeystoneJS 6 автоматизує цей процес: ви описуєте модель даних на TypeScript, а фреймворк генерує GraphQL API, адмін-панель і систему сесій. Ми допомагаємо командам впровадити інтеграцію KeystoneJS в існуючий стек, скорочуючи час backend-розробки до 50%. За 6–8 днів ви отримуєте готову headless CMS на базі KeystoneJS з інтеграцією під Next.js, React або будь-який інший фреймворк.

Чому KeystoneJS зручніший за саморобну GraphQL-прошарку?

Саморобний GraphQL-сервер — це десятки файлів для скалярних полів, мутацій, фільтрів, пагінації. KeystoneJS генерує все автоматично з моделі даних. Типи, мутації, складні фільтри (AND/OR, date-range, related records) — вбудовані за замовчуванням. Економія часу становить до 40%, скорочення коду — на 60%. Прискорення виведення нових типів контенту — з днів до годин.

Які переваги дає KeystoneJS порівняно з Payload CMS?

Критерій KeystoneJS Payload CMS
GraphQL Нативний, перший клас Реалізований, але менш центральний
Адмін-панель Автоматична зі схеми Налаштовувана, більше кастомізації
TypeScript Повна підтримка Повна підтримка
Міграції БД Prisma (авто) Mongoose / SQL через adapter
Аутентифікація Вбудована (сесії, ролі) Вимагає додаткових плагінів

Таким чином, KeystoneJS забезпечує економію бюджету до 40% і скорочення витрат на підтримку до 50%. Ми вибираємо Keystone, коли проект заточений на GraphQL і важлива швидкість розробки. Payload беремо, якщо потрібна максимальна кастомізація адмінки.

Як ми налаштовуємо KeystoneJS під ваш проект

Процес складається з чотирьох етапів: аналітика → проектування → реалізація → тест і деплой.

  1. Аналітика. Визначаємо типи контенту (пости, теги, користувачі, медіа), зв'язки, вимоги до доступів.
  2. Проектування. Розробляємо схему даних на TypeScript. Приклад типової конфігурації:
// keystone.ts
import { config, list } from '@keystone-6/core'
import { allowAll, denyAll, isSignedIn } from '@keystone-6/core/access'
import { text, relationship, password, timestamp, select, checkbox, image, document } from '@keystone-6/core/fields'
import { document as documentField } from '@keystone-6/fields-document'

export default config({
  db: {
    provider: 'postgresql',
    url: process.env.DATABASE_URL!,
    idField: { kind: 'cuid' },
  },
  lists: {
    Post: list({
      access: {
        operation: {
          query: allowAll,
          create: isSignedIn,
          update: isSignedIn,
          delete: isSignedIn,
        },
      },
      fields: {
        title: text({ validation: { isRequired: true } }),
        slug: text({ isIndexed: 'unique' }),
        content: documentField({
          formatting: true,
          dividers: true,
          links: true,
          layouts: [[1, 1], [1, 2, 1]],
        }),
        publishedAt: timestamp(),
        status: select({
          options: ['draft', 'published', 'archived'],
          defaultValue: 'draft',
          ui: { displayMode: 'segmented-control' },
        }),
        author: relationship({ ref: 'User.posts' }),
        tags: relationship({ ref: 'Tag.posts', many: true }),
        cover: image({ storage: 'local_images' }),
      },
      hooks: {
        resolveInput: async ({ resolvedData, operation }) => {
          if (operation === 'create' && !resolvedData.slug) {
            resolvedData.slug = resolvedData.title
              ?.toLowerCase()
              .replace(/\s+/g, '-')
              .replace(/[^a-z0-9-]/g, '')
          }
          return resolvedData
        },
      },
    }),
    Tag: list({
      access: allowAll,
      fields: {
        name: text({ isIndexed: 'unique' }),
        posts: relationship({ ref: 'Post.tags', many: true }),
      },
    }),
    User: list({
      access: {
        operation: {
          query: isSignedIn,
          create: ({ session }) => session?.data?.role === 'admin',
          update: isSignedIn,
          delete: ({ session }) => session?.data?.role === 'admin',
        },
      },
      fields: {
        name: text({ validation: { isRequired: true } }),
        email: text({ isIndexed: 'unique', validation: { isRequired: true } }),
        password: password({ validation: { isRequired: true } }),
        role: select({ options: ['admin', 'editor', 'author'], defaultValue: 'author' }),
        posts: relationship({ ref: 'Post.author', many: true }),
      },
    }),
  },
  session: statelessSessions({
    secret: process.env.SESSION_SECRET!,
    maxAge: 60 * 60 * 24 * 30,
  }),
  storage: {
    local_images: {
      kind: 'local',
      type: 'image',
      generateUrl: (path) => `${process.env.ASSET_BASE_URL}/images${path}`,
      serverRoute: { path: '/images' },
      storagePath: 'public/images',
    },
  },
})
  1. Реалізація. Налаштовуємо аутентифікацію, права доступу, підключаємо сховище файлів. Розробляємо GraphQL-клієнт для frontend-додатка.
  2. Тест і деплой. Перевіряємо всі операції, міграції, продуктивність. Деплоїмо Node.js процес на інфраструктуру замовника.
Етап Тривалість Результат
Аналітика та моделювання 1–2 дні Схема даних, вимоги до доступів
Налаштування KeystoneJS 2–3 дні Робоча адмін-панель, GraphQL API
Інтеграція з frontend 1–2 дні Клієнтські запити, типізація
Тестування та деплой 1–2 дні Перевірка, міграції, запуск

Що входить в роботу

  • Розробка схеми даних (до 6 типів контенту)
  • Налаштування Admin UI (лейбли, фільтри, колонки списку)
  • Реалізація аутентифікації та рольової моделі
  • Підключення файлового сховища (S3 або локальне)
  • Створення GraphQL-клієнта для Next.js з codegen
  • Документація з експлуатації
  • Навчання редакторів (1 година онлайн)
  • Гарантія 30 днів на баги продакшну

Як інтегрувати KeystoneJS з Next.js?

Keystone може працювати як окремий сервіс (бажано в monorepo) або вбудовуватися через API routes. Налаштування клієнта:

// lib/keystoneClient.ts
import { GraphQLClient } from 'graphql-request'

export const keystoneClient = new GraphQLClient(
  process.env.KEYSTONE_API_URL || 'http://localhost:3000/api/graphql',
  {
    headers: {
      'x-api-key': process.env.KEYSTONE_API_KEY!,
    },
  }
)

// Типізовані запити через graphql-codegen
import { getSdk } from './__generated__/sdk'

export const cms = getSdk(keystoneClient)

Приклад сторінки поста з SSG:

// app/blog/[slug]/page.tsx
import { cms } from '@/lib/keystoneClient'

export default async function PostPage({ params }) {
  const { post } = await cms.getPostBySlug({ slug: params.slug })
  if (!post) notFound()
  return <ArticleLayout post={post} />
}

export async function generateStaticParams() {
  const { posts } = await cms.getAllPostSlugs()
  return posts.map(p => ({ slug: p.slug }))
}

Що таке Keystone Document Field?

Keystone використовує власний формат rich text — документ з блоками та інлайн-елементами. Це схоже на Portable Text від Sanity. Для рендерингу використовуємо DocumentRenderer:

import { DocumentRenderer } from '@keystone-6/document-renderer'

function PostContent({ content }) {
  return (
    <DocumentRenderer
      document={content.document}
      renderers={{
        block: {
          paragraph: ({ children, textAlign }) => (
            <p style={{ textAlign }} className="mb-4">{children}</p>
          ),
          layout: ({ layout, children }) => (
            <div className={`grid grid-cols-${layout.join('-')}`}>
              {children}
            </div>
          ),
        },
        inline: {
          link: ({ children, href }) => (
            <a href={href} className="text-blue-600 underline">{children}</a>
          ),
        },
      }}
    />
  )
}

Формат підтримує кастомні layout (сітки колонок), вкладеність, посилання — без ризику XSS, на відміну від HTML-редакторів.

Приклад з практики

Нещодавно ми інтегрували KeystoneJS для великого медіа-проекту. Вихідна ситуація: контент зберігався в WordPress, швидкість публікації нових статей була низькою через складні custom fields. Перехід на KeystoneJS скоротив час публікації на 40%, а навантаження на сервер впало на 25% за рахунок ефективного GraphQL-кешування. Редактори отримали зручну адмін-панель з документ-редактором, а розробники — автогенерований API.

Розшифровка термінів - **GraphQL schema** — опис типів даних та операцій API. - **Resolver** — функція, що повертає дані для запиту. - **Migration** — автоматичне оновлення структури бази даних при зміні схеми.

Згідно з документацією KeystoneJS, міграції виконуються автоматично через Prisma. Це виключає ручну синхронізацію моделі та бази.

Терміни та економія

Базова інтеграція (4–6 типів контенту + аутентифікація + GraphQL-клієнт) — 6–8 днів. Розширена (з кастомними хуками, S3-сховищем, codegen) — до 12 днів. Економія бюджету на backend-розробку становить до 40%, а скорочення витрат на підтримку API — до 50% за рахунок генерації коду.

Вартість розраховується індивідуально під кожен проект. У нас за плечима понад 30 успішних інтеграцій CMS і 5 років досвіду в headless-рішеннях. Зв'яжіться з нами — оцінимо задачу за один робочий день. Замовте консультацію прямо зараз і отримайте попередній розрахунок термінів.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060