Интеграция 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-подобные эндпоинты и административный интерфейс. Ближайший аналог по подходу — 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 дней.