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

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Інтеграція CMS KeystoneJS для управління контентом
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

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

KeystoneJS 6 — це headless CMS та application framework в одному. Конфіг схеми пишеться на TypeScript, з неї автоматично генеруються GraphQL API, REST-подібні endpoints та адміністративний інтерфейс. Найближчий аналог за підходом — Payload, але Keystone ставить GraphQL в центр архітектури.

Сильні сторони Keystone

Автоматична генерація GraphQL-схеми з моделі даних економить багато ручної роботи. Типи, мутації, фільтри, пагінація — все з'являється само. Плюс вбудована підтримка сесій, аутентифікації, ролей — збирати це вручну не потрібно.

Працює з PostgreSQL та SQLite через Prisma — міграції генеруються автоматично.

Установа

npm create keystone-app@latest my-project
cd my-project
npm install

Або додавання в наявний проект:

npm install @keystone-6/core

Схема даних

// 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',
    },
  },
})

GraphQL API — приклади запитів

З цієї схеми Keystone автоматично генерує:

# Отримати опубліковані пости з тегами
query {
  posts(
    where: { status: { equals: "published" } }
    orderBy: { publishedAt: desc }
    take: 10
  ) {
    id
    title
    slug
    publishedAt
    author {
      name
    }
    tags {
      name
    }
    cover {
      url
      width
      height
    }
  }
}

# Створити пост
mutation {
  createPost(data: {
    title: "Новий пост"
    content: { document: [] }
    status: "draft"
    author: { connect: { id: "cuid123" } }
    tags: { connect: [{ id: "tag1" }, { id: "tag2" }] }
  }) {
    id
    slug
  }
}

Фільтрація підтримує складні умови:

query {
  posts(where: {
    AND: [
      { status: { equals: "published" } },
      { publishedAt: { lte: "2025-01-01T00:00:00Z" } },
      { tags: { some: { name: { equals: "TypeScript" } } } }
    ]
  }) {
    id
    title
  }
}

Інтеграція з Next.js

Keystone можна піднути окремим процесом або вбудувати в Next.js через next.config.js. Для monorepo — окремий сервіс переважніший:

// 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)
// 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

Вбудований rich text — не просто рядок, а структурований документ (подібно до Portable Text):

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>
          ),
        },
      }}
    />
  )
}

Деплой

Keystone — це Node.js процес. Для production:

keystone build   # збірка
keystone start   # запуск

На Railway, Fly.io, Render — dockerfile стандартний. PostgreSQL через Supabase або власний інстанс. Змінні оточення: DATABASE_URL, SESSION_SECRET, ASSET_BASE_URL.

Терміни

Стандартна інтеграція з 4–6 типами контенту, настройкою аутентифікації та GraphQL-клієнтом в Next.js: 6–8 днів. З настройкою codegen, кастомними хуками та S3-сховищем: до 12 днів.