Ручная разработка 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 под ваш проект
Процесс состоит из четырёх этапов: аналитика → проектирование → реализация → тест и деплой.
- Аналитика. Определяем типы контента (посты, теги, пользователи, медиа), связи, требования к доступам.
- Проектирование. Разрабатываем схему данных на 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',
},
},
})
- Реализация. Настраиваем аутентификацию, права доступа, подключаем хранилище файлов. Разрабатываем GraphQL-клиент для frontend-приложения.
- Тест и деплой. Проверяем все операции, миграции, производительность. Деплоим 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-решениях. Свяжитесь с нами — оценим задачу за один рабочий день. Закажите консультацию прямо сейчас и получите предварительный расчёт сроков.







