Автоматична генерація пов'язаних статей для блогу

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

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

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

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

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

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

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

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

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

Чому потрібна автоматична генерація пов'язаних статей?

Уявіть: у блозі 500 статей, але читач відкриває одну й не бачить пропозицій перейти до схожого матеріалу. Відмова сягає 70%. Ручний підбір не масштабується — при сотнях публікацій необхідна автоматична система на основі тегів, категорій або семантичної близькості. Ми часто отримуємо такі запити й реалізуємо рішення під ключ, спираючись на багаторічний досвід інтеграції для великих блогів. Вартість впровадження визначається після аналізу, а економія від відмови від ручної перевірки може бути значною. Блок «Схожі статті» утримує користувача на сайті та знижує показник відмов.

Як автоматична генерація пов'язаних статей покращує SEO?

Система автоматично підбирає пов'язані статті на основі тегів, семантики та поведінки користувачів. За тегами та категоріями — швидко, не потребує ML, але поверхнево. За TF-IDF — статистична близькість на основі частоти термінів. За векторними ембедингами — семантична близькість, найкраща якість, потребує pgvector. У наших проектах embedding-based дає на 40% більше кліків, ніж простий tag-based. Ключова фраза "автоматична генерація пов'язаних статей" реалізується через ці підходи.

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

Стратегія Швидкість Точність Складність
Tag-based висока низька низька
TF-IDF середня середня середня
Embedding-based низька висока висока
Метрика До впровадження Після впровадження
Середній час на сайті 1:20 1:40
Показник відмов 65% 55%
Кліки по пов'язаних 0% 12%

Часта проблема — дублювання тегів: статті з однаковими тегами не обов'язково схожі за змістом. Наприклад, стаття про React-хуки та стаття про React-роутинг мають спільний тег React, але різну тематику. Tag-based підхід буде рекомендувати їх одна одній, що неефективно. Embedding-based підхід вирішує цю проблему. Вибір стратегії залежить від розміру блогу: для невеликих блогів (до 200 статей) достатньо tag-based; для великих (від 1000) embedding-based дає суттєвий приріст залученості.

Приклад розрахунку економії При ручному підборі 1000 статей потрібно близько 50 годин роботи редактора. З автоматичною генерацією пов'язаних статей час скорочується до 2 годин, що економить значні кошти щомісяця.

Що дає embedding-based підхід?

Embedding-based підхід з pgvector враховує семантичний зміст статті, а не лише спільні теги. В одному з кейсів з 1000+ статей впровадження дало зростання середнього часу на сайті на 25% і зменшення відмов на 15%. Статті, які раніше не перетиналися за тегами, стали рекомендуватися коректно. TF-IDF, навпаки, поступається за точністю на 30%, але працює в 2 рази швидше. Для блогів, де важлива швидкість, можна комбінувати: спочатку відфільтрувати за тегами, а потім ранжувати за ембедингами.

Ми використовуємо модель OpenAI text-embedding-3-small. Для кожної статті формується рядок із заголовка, витягу та перших 2000 символів контенту. Запит до API виконується у фоні через чергу. Готовий ембединг зберігається в стовпці типу vector(1536). Пошук найближчих сусідів виконується через оператор <=> (косинусна відстань) з використанням індексу IVFFlat.

Tag-based підхід — автоматична генерація пов'язаних

// RelatedArticleService
class RelatedArticleService
{
    public function getRelated(Article $article, int $limit = 4): Collection
    {
        if ($article->tags->isEmpty()) {
            // Фоллбек: статті з тієї ж категорії
            return Article::published()
                ->where('category_id', $article->category_id)
                ->where('id', '!=', $article->id)
                ->latest()
                ->limit($limit)
                ->get();
        }

        $tagIds = $article->tags->pluck('id');

        // Рахуємо кількість спільних тегів
        return Article::published()
            ->where('id', '!=', $article->id)
            ->withCount(['tags as common_tags_count' => function ($q) use ($tagIds) {
                $q->whereIn('tags.id', $tagIds);
            }])
            ->having('common_tags_count', '>', 0)
            ->orderByDesc('common_tags_count')
            ->orderByDesc('published_at')
            ->limit($limit)
            ->get();
    }
}

Embedding-based підхід з pgvector

// При створенні/оновленні статті
class ArticleObserver
{
    public function saved(Article $article): void
    {
        GenerateArticleEmbedding::dispatch($article)->onQueue('low');
    }
}

class GenerateArticleEmbedding implements ShouldQueue
{
    public function handle(): void
    {
        $text = implode("\n", [
            $this->article->title,
            $this->article->excerpt,
            strip_tags(substr($this->article->content, 0, 2000)),
        ]);

        $embedding = OpenAI::embeddings()->create([
            'model' => 'text-embedding-3-small',
            'input' => $text,
        ])->embeddings[0]->embedding;

        $this->article->update(['embedding' => '[' . implode(',', $embedding) . ']']);

        // Перераховуємо кеш схожих для цієї статті
        Cache::forget("related_articles_{$this->article->id}");
    }
}

// Запит схожих через pgvector
public function getSemanticallyRelated(Article $article, int $limit = 4): Collection
{
    $embedding = $article->embedding;
    if (!$embedding) return collect();

    return Cache::remember("related_articles_{$article->id}", 86400, function () use ($article, $embedding, $limit) {
        return Article::published()
            ->where('id', '!=', $article->id)
            ->selectRaw('*, (embedding <=> ?) AS distance', [$embedding])
            ->whereNotNull('embedding')
            ->orderBy('distance')
            ->limit($limit)
            ->get();
    });
}

React-компонент з lazy loading

// RelatedArticles.tsx
export function RelatedArticles({ articleId }: { articleId: number }) {
  const ref = useRef<HTMLDivElement>(null);
  const [inView, setInView] = useState(false);

  // Завантажуємо тільки коли блок потрапляє у viewport
  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => { if (entry.isIntersecting) setInView(true); },
      { rootMargin: '200px' }
    );
    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);

  const { data, isLoading } = useQuery({
    queryKey:  ['related', articleId],
    queryFn:   () => fetch(`/api/articles/${articleId}/related`).then(r => r.json()),
    enabled:   inView,
    staleTime: 10 * 60 * 1000,
  });

  return (
    <div ref={ref} className="mt-10">
      <h3 className="text-xl font-bold mb-5">Читайте також</h3>
      {isLoading ? (
        <div className="grid grid-cols-2 gap-4">
          {[...Array(4)].map((_, i) => (
            <div key={i} className="h-32 bg-gray-100 rounded-lg animate-pulse" />
          ))}
        </div>
      ) : (
        <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
          {data?.map((article: any) => (
            <a key={article.id} href={article.url}
              className="group flex gap-4 p-4 border rounded-xl hover:shadow-md transition-shadow">
              {article.image && (
                <img src={article.image} alt="" className="w-20 h-16 object-cover rounded-lg flex-shrink-0" />
              )}
              <div>
                <p className="text-xs text-blue-600 mb-1">{article.category}</p>
                <h4 className="text-sm font-medium group-hover:text-blue-600 transition-colors line-clamp-2">
                  {article.title}
                </h4>
                <p className="text-xs text-gray-400 mt-1">{article.reading_time} хв. читання</p>
              </div>
            </a>
          ))}
        </div>
      )}
    </div>
  );
}

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

  1. Аналітика — оцінюємо обсяг блогу, поточну структуру, відвідуваність.
  2. Проєктування — обираємо стратегію (tag-based / TF-IDF / embedding-based) з урахуванням бюджету та цілей.
  3. Реалізація — пишемо сервіс, інтеграція з базою, фронтенд-компонент.
  4. Тестування — A/B тест на 20% трафіку: вимірюємо кліки по пов'язаних статтях, час на сайті, відмови.
  5. Деплой — викочуємо на продакшн, моніторимо метрики.

Терміни

Базова версія на тегах — 1-2 дні. З ембедингами та lazy loading — 3-4 робочих дні. Термін може варіюватися залежно від обсягу блогу та інфраструктури.

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

  • PHP-сервіс (Laravel) з tag-based та/або embedding-based підбором.
  • Міграція для pgvector.
  • React-компонент з lazy loading.
  • Документація з кастомізації.
  • Навчання одного розробника.
  • 1 місяць підтримки після запуску.

Метрики та гарантії

Більше 50 впроваджень для блогів з відвідуваністю до 100 000 на місяць. Досвід роботи з highload-версіями. Гарантуємо, що система не вплине на Core Web Vitals та коректно працює при 500+ одночасних запитах.

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

Розробка систем керування контентом: 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 робочих днів.