Разработка сайта на CMS KeystoneJS

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка сайта на CMS KeystoneJS
Сложная
~1-2 недели
Часто задаваемые вопросы

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

Этапы разработки

Последние работы

  • 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 — это не SaaS-платформа и не готовый движок, а code-first headless CMS: вы описываете модели данных на TypeScript, а система генерирует GraphQL API, Admin UI и схему базы данных автоматически. Это делает KeystoneJS особенно сильным выбором для проектов с нестандартной доменной логикой, где гибкость важнее скорости первоначальной настройки.

Когда выбирать KeystoneJS

KeystoneJS подходит, когда:

  • Нужен полный контроль над схемой данных и бизнес-логикой
  • Команда комфортно работает с Node.js и TypeScript
  • Требуется кастомная логика доступа, сложные вычисляемые поля, хуки на уровне данных
  • GraphQL API — первичный интерфейс для фронтенда

Не подходит, если нужен хостинг «из коробки» без DevOps или проект требует богатого UI для нетехнических редакторов (тогда смотрите на Strapi или Payload).

Структура проекта

my-keystone-project/
├── keystone.ts          # Точка входа: конфиг БД, сессий, UI
├── schema.ts            # Агрегирует все Lists
├── lists/
│   ├── Post.ts
│   ├── Author.ts
│   ├── Tag.ts
│   └── Category.ts
├── auth.ts              # Настройка аутентификации
├── migrations/          # Prisma-миграции
└── frontend/            # Next.js или любой другой фронтенд

Конфигурация и запуск

// keystone.ts
import { config } from '@keystone-6/core';
import { lists } from './schema';
import { withAuth, session } from './auth';

export default withAuth(
  config({
    db: {
      provider: 'postgresql',
      url: process.env.DATABASE_URL!,
      enableLogging: process.env.NODE_ENV === 'development',
      idField: { kind: 'uuid' },
    },
    lists,
    session,
    ui: {
      // Ограничение доступа к Admin UI
      isAccessAllowed: (context) => !!context.session?.data,
    },
    server: {
      cors: {
        origin: [process.env.FRONTEND_URL!],
        credentials: true,
      },
    },
  })
);

Моделирование контента

// lists/Post.ts
import { list } from '@keystone-6/core';
import { allowAll, denyAll } from '@keystone-6/core/access';
import {
  text, relationship, timestamp, select,
  checkbox, image, document,
} from '@keystone-6/core/fields';
import { document as documentField } from '@keystone-6/fields-document';

export const Post = list({
  access: {
    operation: {
      query: allowAll,
      create: ({ session }) => !!session,
      update: ({ session }) => !!session,
      delete: ({ session }) => session?.data?.role === 'admin',
    },
  },
  fields: {
    title: text({ validation: { isRequired: true } }),
    slug: text({
      validation: { isRequired: true },
      isIndexed: 'unique',
      hooks: {
        resolveInput: ({ resolvedData, inputData }) => {
          if (inputData.title && !inputData.slug) {
            return inputData.title
              .toLowerCase()
              .replace(/[^a-z0-9]+/g, '-')
              .replace(/(^-|-$)/g, '');
          }
          return resolvedData.slug;
        },
      },
    }),
    status: select({
      options: [
        { label: 'Draft', value: 'draft' },
        { label: 'Published', value: 'published' },
        { label: 'Archived', value: 'archived' },
      ],
      defaultValue: 'draft',
      ui: { displayMode: 'segmented-control' },
    }),
    publishedAt: timestamp(),
    content: documentField({
      formatting: true,
      links: true,
      dividers: true,
      layouts: [[1, 1], [1, 1, 1]],
    }),
    author: relationship({
      ref: 'Author.posts',
      ui: { displayMode: 'select' },
    }),
    tags: relationship({
      ref: 'Tag.posts',
      many: true,
      ui: { displayMode: 'cards', cardFields: ['name'] },
    }),
    featuredImage: image({ storage: 'local_images' }),
    seoTitle: text(),
    seoDescription: text({ ui: { displayMode: 'textarea' } }),
  },
  hooks: {
    beforeOperation: async ({ operation, item, resolvedData, context }) => {
      if (operation === 'update' && resolvedData.status === 'published') {
        resolvedData.publishedAt = new Date();
      }
    },
  },
  ui: {
    listView: {
      initialColumns: ['title', 'status', 'author', 'publishedAt'],
      initialSort: { field: 'publishedAt', direction: 'DESC' },
    },
  },
});

GraphQL API: работа с фронтендом

После запуска npx keystone dev доступен GraphQL Playground на http://localhost:3000/api/graphql.

# Запрос опубликованных постов с пагинацией
query GetPosts($skip: Int, $take: Int) {
  posts(
    where: { status: { equals: "published" } }
    orderBy: { publishedAt: desc }
    skip: $skip
    take: $take
  ) {
    id
    title
    slug
    publishedAt
    author {
      name
      avatar {
        url
      }
    }
    tags {
      name
      slug
    }
  }
  postsCount(where: { status: { equals: "published" } })
}

В Next.js с Apollo Client или urql:

// lib/keystoneClient.ts
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';

export const keystoneClient = new ApolloClient({
  link: createHttpLink({ uri: process.env.KEYSTONE_API_URL }),
  cache: new InMemoryCache(),
  defaultOptions: {
    query: { fetchPolicy: 'network-only' },
  },
});

Хранилище файлов и изображений

// keystone.ts — настройка хранилищ
storage: {
  local_images: {
    kind: 'local',
    type: 'image',
    generateUrl: (path) => `${process.env.BASE_URL}/images${path}`,
    serverRoute: { path: '/images' },
    storagePath: 'public/images',
  },
  s3_files: {
    kind: 's3',
    type: 'file',
    bucketName: process.env.S3_BUCKET!,
    region: process.env.S3_REGION!,
    accessKeyId: process.env.S3_ACCESS_KEY!,
    secretAccessKey: process.env.S3_SECRET!,
    signed: { expiry: 3600 },
  },
},

Аутентификация и сессии

KeystoneJS использует stateful-сессии через cookie. Конфигурация аутентификации:

// auth.ts
import { createAuth } from '@keystone-6/auth';
import { statelessSessions } from '@keystone-6/core/session';

const { withAuth } = createAuth({
  listKey: 'User',
  identityField: 'email',
  secretField: 'password',
  initFirstItem: {
    fields: ['name', 'email', 'password'],
  },
  sessionData: 'id name email role',
  passwordResetLink: {
    sendToken: async ({ itemId, identity, token, context }) => {
      await sendPasswordResetEmail(identity, token);
    },
  },
});

export const session = statelessSessions({
  maxAge: 60 * 60 * 24 * 30,
  secret: process.env.SESSION_SECRET!,
});

Деплой

KeystoneJS разворачивается как обычное Node.js-приложение. Требования: PostgreSQL или MySQL, Node.js 18+, достаточно памяти для процесса (~256MB).

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npx keystone build
EXPOSE 3000
CMD ["npx", "keystone", "start"]

Для Railway, Render, Fly.io — стандартный Node.js deploy. Необходимо запустить keystone prisma migrate deploy перед стартом в production.

Сроки разработки типового сайта

Этап Время
Установка, конфиг БД, базовые Lists 1 день
3–5 моделей с отношениями 2–3 дня
Настройка доступа и ролей 1 день
Аутентификация + сессии 0.5 дня
Интеграция с Next.js фронтендом 2–3 дня
Деплой + миграции 0.5–1 день
Итого для среднего сайта 7–10 дней

Для корпоративных проектов со сложными правами доступа, мультисайтовостью и CI/CD — 3–5 недель.