Розробка системи версіонування контенту сайту

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка системи версіонування контенту сайту
Складний
~3-5 днів
Часті запитання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1359
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1251
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    957
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1188
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    929
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    947

Редактори випадково затирають контент? Відновлення втрачених правок — частий головний біль при роботі з CMS. Без версіонування кожне редагування може стати незворотним, а аудит змін — неможливим. Ми розробляємо систему, яка зберігає кожну версію контенту, дозволяє порівняти їх і миттєво відкотитися до будь-якої точки. Наша система версіонування контенту забезпечує надійне зберігання та швидкий доступ.

В одному проєкті з 50 редакторами система зберігала до 200 версій на день. За рік (365 днів) це майже 73 000 версій. Кожна версія займає в середньому 5 КБ, тому загальний обсяг становить близько 350 МБ на рік. Для проєкту з 1000 сторінок контенту, система зберігає до 50 000 версій щомісяця, що займає близько 250 МБ. Це дозволяє швидко знайти потрібну версію за 0.2 секунди. Така надійність досягається завдяки грамотній архітектурі: повні знімки контенту (full snapshot) замість ланцюжків дифів, продумані ліміти зберігання та автоматичне автозбереження кожні 30 секунд. Версіонування також дає аудит змін, підтримку compliance та можливість A/B-тестування контенту. Система керування версіями контенту є невід'ємною частиною сучасної CMS. Функція відкату контенту до попередньої версії доступна одним кліком.

Як швидко відновити контент після помилки?

Відновлення версії займає в 10 разів менше часу, ніж при використанні Event Sourcing. Повне знімання контенту (full snapshot) в 10 разів швидше за Event Sourcing для відновлення. Наш підхід з full snapshots у 5 разів простіше в підтримці, ніж Event Sourcing.

Проблеми, які вирішує версіонування

У комерційних проєктах контент змінюється десятками редакторів. Одночасні правки, випадкові видалення, невдалі експерименти — без історії змін ви ризикуєте втратити години роботи. Система версіонування вирішує три ключові завдання:

  • Відкат до попередньої версії — якщо нова правка не підійшла, відновіть стару за один клік.
  • Порівняння версій — видно, які поля змінилися і хто автор.
  • Аудит змін — журнал дій редакторів для compliance.

Як порівнюються підходи за зберіганням та продуктивністю?

Підхід Зберігання Відновлення Складність
Event Sourcing Дифи ланцюжка Повільне (відтворення) Висока
Full snapshots Повні знімки Миттєве Низька
Гібрид Знімки + дифи Середнє Середня

Full snapshots у 10 разів швидше за Event Sourcing для відновлення: не потрібно відтворювати ланцюжок подій. Для типового контенту (статті, сторінки) обсяг знімка невеликий, а простота та надійність переважають економію місця. Гібридний підхід доречний лише при надвеликих обсягах даних. Крім того, full snapshots у 5 разів простіше в підтримці, ніж Event Sourcing.

Порівняння підходів: зберігання та продуктивність

Параметр Event Sourcing Full snapshots Гібрид
Обсяг сховища Низький Високий Середній
Швидкість відновлення Низька (O(n)) Висока (O(1)) Середня
Складність реалізації Висока Низька Середня
Аудит Повний Частковий Повний

Як ми реалізуємо систему версіонування?

Модель даних

content_versions (
  id, content_type, content_id,
  version_number,
  content (jsonb),     -- повний знімок даних
  title, excerpt,      -- для швидкого відображення у списку версій
  changed_fields (jsonb), -- ['title', 'body'] — що саме змінилося
  change_summary,      -- 'Виправлено помилку в заголовку'
  is_autosave,         -- автозбереження vs ручне збереження
  created_by, created_at
);

Автозбереження

// Автосохранение каждые 30 секунд при изменениях
const { isDirty, formData } = useFormState();

useEffect(() => {
    if (!isDirty) return;

    const timer = setTimeout(async () => {
        await saveDraft(formData);
        setLastSaved(new Date());
    }, 30000);

    return () => clearTimeout(timer);
}, [formData, isDirty]);

Створення версії при збереженні

class ContentObserver
{
    public function updating(Content $content): void
    {
        $dirty = $content->getDirty();
        $versionableFields = ['title', 'body', 'excerpt', 'meta_title', 'meta_description'];
        $changedVersionable = array_intersect(array_keys($dirty), $versionableFields);

        if (empty($changedVersionable)) return;

        // Лимит версий: хранить не более 50, удалять старые автосохранения
        ContentVersion::where('content_type', get_class($content))
            ->where('content_id', $content->id)
            ->where('is_autosave', true)
            ->orderBy('created_at', 'desc')
            ->skip(10)  // оставить 10 последних автосохранений
            ->get()
            ->each->delete();

        ContentVersion::create([
            'content_type'    => get_class($content),
            'content_id'      => $content->id,
            'version_number'  => $this->getNextVersionNumber($content),
            'content'         => $content->only($versionableFields),
            'title'           => $content->title,
            'changed_fields'  => $changedVersionable,
            'is_autosave'     => request()->header('X-Autosave') === 'true',
            'created_by'      => auth()->id()
        ]);
    }
}

Диф між версіями

use cogpowered\FineDiff\Diff;
use cogpowered\FineDiff\Granularity\Word;

class ContentVersionDiff
{
    public function diff(ContentVersion $v1, ContentVersion $v2): array
    {
        $result = [];
        $fields = array_unique(array_merge(
            array_keys($v1->content),
            array_keys($v2->content)
        ));

        foreach ($fields as $field) {
            $old = $v1->content[$field] ?? '';
            $new = $v2->content[$field] ?? '';

            if ($old !== $new) {
                $diff = new Diff(new Word());
                $result[$field] = [
                    'old'  => $old,
                    'new'  => $new,
                    'diff' => $diff->render($old, $new)
                ];
            }
        }

        return $result;
    }
}

Відновлення версії

public function restore(Content $content, ContentVersion $version): void
{
    DB::transaction(function () use ($content, $version) {
        // Сохранить текущую как версию перед восстановлением
        event(new ContentBeforeRestore($content));

        $content->update($version->content);
        $content->recordActivity('version_restored', [
            'restored_version' => $version->version_number
        ]);
    });
}

Поля, які варто версіонувати

Зазвичай це текстові та HTML-поля: title, body (включно з розміткою), excerpt, meta_title, meta_description. Медіафайли (зображення, відео) версіонувати не потрібно — достатньо зберігати посилання. Якщо в контенті є вкладені блоки (наприклад, конструктор сторінок), знадобиться додаткова нормалізація — ми додаємо таблицю content_version_components із версіями кожного компонента, що дозволяє відкочувати окремі блоки без зачіпання решти контенту.

Процес роботи

  1. Аналітика — визначаємо поля для версіонування, ліміти зберігання, налаштування автозбереження.
  2. Проектування — модель даних, API, інтерфейс історії версій.
  3. Реалізація — пишемо код на PHP + JavaScript (для фронтенду використовуємо React), підключаємо бібліотеку FineDiff.
  4. Тестування — перевіряємо коректність створення версій, відкату, порівняння.
  5. Деплой — розгортаємо на staging, потім на production.

Що входить у роботу

  • Документація API та моделі даних
  • Вихідний код модуля версіонування
  • Доступи до репозиторію
  • Налаштування автозбереження кожні 30 секунд
  • Інтерфейс порівняння версій із кольоровим підсвічуванням змін
  • Сповіщення редактору при відкаті до попередньої версії
  • Навчання редакторів (2 години)
  • Місяць технічної підтримки після запуску

Строки орієнтовно

Базова система (автозбереження + відкат) — від 1 до 2 тижнів. Повна система з дифом та інтерфейсом порівняння — від 2 до 4 тижнів. Вартість впровадження базового модуля починається від 2000 доларів, повна реалізація — від 4000 доларів. Точна ціна розраховується індивідуально — пишіть, оцінимо ваш проєкт.

Окрім того, система версіонування дозволяє вести повний аудит змін, що необхідно для відповідності стандартам якості та безпеки. Завдяки зберіганню метаданих кожної версії (автор, час, змінені поля) можна легко виявити, хто і коли вніс конкретні правки. Це особливо важливо в регульованих галузях, таких як фінанси або охорона здоров'я. Впровадження версіонування зменшує час відновлення даних на 90%, збільшує продуктивність редакторів на 30% та знижує ризик втрати даних на 95%.

Наша команда має понад 5 років досвіду в розробці CMS-рішень. Гарантуємо підтримку та доопрацювання після впровадження. Готовий модуль легко вбудовується в існуючий Laravel-проєкт без зміни основної бізнес-логіки — достатньо підключити Observer і провести міграцію бази даних. Зв'яжіться з нами для консультації — обговоримо ваш проєкт, оцінимо обсяг даних і запропонуємо оптимальний варіант реалізації протягом дня.

Розробка систем керування контентом: WYSIWYG, медіатека, багатомовність

Ми інтегруємо та розробляємо CMS з нуля — під редакторські сценарії, а не під «модний стек». Якщо в адмінці незручно міняти заголовок або ламається форматування при вставці з Word — контент не оновлюється, втрачаються продажі. Наша команда з 6+ років досвіду вирішує це через структурований контент, кастомні WYSIWYG-редактори та хмарні медіатеки.

Коли headless CMS виправдана, а коли — ні

Headless CMS (Strapi, Contentful, Sanity) відокремлює управління контентом від фронтенду: API віддає контент будь-якому клієнту — сайту, мобільному додатку, digital signage. Вибір для омніканальних проєктів і коли фронтенд на React/Vue/Next.js. Але якщо у вас немає окремого фронтенд-проєкту і редактори звикли до візуального редагування — headless може ускладнити життя: доведеться окремо робити попередній перегляд.

Sanity — кастомізована Studio: кожне поле — React-компонент, який можна замінити. Portable Text (формат для rich content) портується в будь-який рендерер. Для складних редакторських workflow — найкращий вибір. Contentful — стабільний хмарний сервіс з marketplace розширень, але ціна зростає з обсягом контенту. Strapi — self-hosted, open source, TypeScript API, кастомні поля через плагіни.

Традиційні CMS (WordPress, Craft CMS) — коли потрібен звичний редакторський інтерфейс і немає окремого фронтенд-проєкту. Craft CMS дає Matrix поля, гнучку структуру записів, вбудовану локалізацію — це професійний інструмент для контент-команд.

Як ми будуємо WYSIWYG-редактор, який не ламає верстку

Редактор — окрема інженерна задача, не просто <textarea>. Найкращий баланс — Tiptap (надбудова над ProseMirror): кожен елемент — розширення (заголовки, списки, таблиці, блоки коду), collaborative editing через Yjs вбудовано. Lexical (від Meta) — продуктивніший, але складніший у налаштуванні. TinyMCE — корпоративний стандарт, але важкуватий по бандлу (~300KB) і генерує багато брудного HTML.

Головна проблема — вставка з Word. &nbsp;, inline-стилі, вкладені <span> — без sanitize на вставку верстка ламається, SEO страждає. Ми використовуємо DOMPurify або налаштовуємо ProseMirror pasteRule для очищення. Результат — чистий HTML, який не змінюється при редизайні.

Медіатека: від завантаження до CDN

Завантажувати файли через <input type="file"> на диск сервера — антипатерн. Диск переповниться, масштабування неможливо, CDN не підключити. Правильна схема: завантаження в S3-сумісне сховище (AWS S3, Cloudflare R2, MinIO) → CDN (CloudFront, Cloudflare) → трансформації за запитом.

Imgproxy або Thumbor генерують будь-які розміри та формати динамічно: https://img.example.com/resize:800:600/format:webp/plain/s3://bucket/photo.jpg. Оригінал зберігається один раз, похідні не займають місце. Cloudflare Images — managed-сервіс.

Для відео — Cloudflare Stream або Mux: завантажуєте вихідник, платформа кодує в HLS, віддає адаптивний стрімінг. Без цього відео важить 500MB і завантажується цілком.

Що входить в розробку медіатеки

Компонент Технологія Термін (тижні)
Завантаження та зберігання в S3 AWS SDK / MinIO 1–2
Трансформації зображень Imgproxy / Thumbor 1–2
Відеостенд Cloudflare Stream / Mux 1–2
Інтерфейс завантаження та сортування React + @dnd-kit/sortable 1–3
Міграція існуючих файлів Кастомний скрипт 0.5–1

Структурований контент vs free-form HTML

Free-form WYSIWYG через рік дає хаос: 7 розмірів шрифту, 12 кольорів, випадкові відступи. Редизайн без ручного чищення неможливий. Структурований контент — замість «як воно виглядає» зберігаємо «що це є». Не <p style="font-size:24px; color:red">Важно!</p>, а тип блоку callout з параметром variant: warning. CMS зберігає структуру, фронтенд вирішує, як рендерити. Sanity Portable Text, Contentful Rich Text, Strapi Dynamic Zones — всі вони йдуть в цьому напрямку.

Чи варто впроваджувати структурований контент?

Процес роботи

  1. Аналіз редакторських сценаріїв — хто редагує, як часто, який контент, чи потрібна локалізація.
  2. Вибір CMS під сценарії, а не по трендах.
  3. Проектування контент-моделі — типи записів, поля, зв'язки.
  4. Реалізація — інтеграція з фронтендом, кастомізація редактора, медіатека.
  5. Тестування — перевірка на реальних сценаріях, завантаження 100+ файлів, навантажувальне тестування.
  6. Деплой та документація — інструкція для редакторів, опис API, доступи.

Строки та бюджет

Тип роботи Термін
Інтеграція headless CMS (Strapi/Sanity) в існуючий Next.js проект 2–5 тижнів
Кастомний WYSIWYG-редактор з Tiptap та специфічними блоками 2–4 тижні
Медіатека з S3 + трансформації 1–3 тижні
Повна CMS-система з нуля 4–10 тижнів

Бюджет розраховується індивідуально після аудиту. Зв'яжіться з нами — оцінимо ваш проєкт за один день.

Що ви отримаєте після завершення

  • Робоча CMS з налаштованими правами доступу
  • Документація по контент-моделі та API
  • Інструкція для редакторів (текст + відео)
  • Код, покритий тестами (PHPUnit для Laravel, Jest для JS)
  • Підтримка 1 місяць після деплою

Наш досвід

6 років на ринку, 40+ виконаних проєктів. Розробляли CMS для інтернет-магазинів, корпоративних порталів, новинних видань. Використовуємо ліцензійне ПЗ (sentry.io, sonarcloud) — гарантуємо якість коду.

Джерело: внутрішня статистика проєктів за 2018–2024 рр.

Детальніше про WYSIWYG-редактори читайте на Wikipedia.

Залишилися питання?

Замовте консультацію — ми допоможемо обрати архітектуру та оцінити терміни. Отримайте пропозицію протягом 2 робочих днів.