Розробка сайту на 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 тижнів.