Розробка віджета контекстних опитувань (in-app survey) для сайту

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

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Розробка віджета контекстних опитувань (in-app survey) для сайту
Середній
~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

Уявіть: користувач щойно скасував підписку на Pro-тариф. За хвилину він отримує email із проханням відповісти, чому пішов. Шанс, що він відкриє цей лист — менше 5%. In-app survey вирішує цю проблему: запитання з'являється прямо в інтерфейсі одразу після скасування. Конверсія у відповідь підскакує до 30%. Саме це дає in-app survey — опитування, вбудоване в контекст дії.

Ми 5+ років розробляємо складні веб-додатки та реалізували in-app опитування для більш ніж 20 проєктів. Гарантуємо, що система не вплине на Core Web Vitals і не створить зайвих запитів до API. Як показує практика, впровадження in-app survey окупається в середньому за 2-3 місяці за рахунок зростання утримання на 15-20%.

Які проблеми вирішує in-app survey?

Email-опитування втрачають до 90% цільової аудиторії: листи потрапляють у спам, забуваються, відкриваються не в тому контексті. In-app survey вирішує цю проблему — користувач відповідає в момент, коли враження про функцію ще свіже. Результат: точний зворотний зв'язок, NPS з прив'язкою до дії, менше відмов при зборі даних.

Порівняння ефективності: in-app проти email

За даними досліджень, конверсія in-app опитувань у 3-6 разів вища, ніж email. Причина — скорочення шляху: не потрібно переходити за посиланням, вводити логін. Все відбувається в одному вікні. Наш досвід показує, що навіть коротке опитування з 3 запитань збирає більше 200 відповідей за тиждень на аудиторії в 1000 користувачів.

Як влаштована система опитувань?

Система будується на двох компонентах: бекенд на Laravel (таблиці survey, questions, responses) і фронтенд-віджет на React. Двигун перевіряє умови показу: подію, план користувача, мінімальну кількість сесій, cooldown між відповідями.

Схема бази даних

Schema::create('surveys', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->string('trigger_event');        // 'feature_used', 'upgrade_cancelled', 'idle_30_days'
    $table->json('trigger_conditions');     // {"min_sessions": 3, "plan": ["pro", "enterprise"]}
    $table->integer('delay_seconds')->default(0);
    $table->integer('cooldown_days')->default(30);
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

Schema::create('survey_questions', function (Blueprint $table) {
    $table->id();
    $table->foreignId('survey_id')->constrained()->cascadeOnDelete();
    $table->text('text');
    $table->enum('type', ['single_choice', 'multi_choice', 'text', 'scale', 'nps']);
    $table->json('options')->nullable();
    $table->integer('order')->default(0);
});

Schema::create('survey_responses', function (Blueprint $table) {
    $table->id();
    $table->foreignId('survey_id')->constrained();
    $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
    $table->json('answers');                // {"q_1": "option_a", "q_2": "Great product"}
    $table->string('trigger_event')->nullable();
    $table->timestamps();
});

API: отримання актуального опитування

class SurveyController extends Controller
{
    public function getForEvent(Request $request): JsonResponse
    {
        $event = $request->input('event');
        $user  = auth()->user();

        $survey = Survey::where('trigger_event', $event)
            ->where('is_active', true)
            ->get()
            ->first(function ($survey) use ($user) {
                // Перевіряємо cooldown
                $lastResponse = SurveyResponse::where('survey_id', $survey->id)
                    ->where('user_id', $user->id)
                    ->latest()->first();

                if ($lastResponse && $lastResponse->created_at->diffInDays(now()) < $survey->cooldown_days) {
                    return false;
                }

                // Перевіряємо умови
                $conditions = $survey->trigger_conditions;
                if (isset($conditions['plan']) && !in_array($user->plan, $conditions['plan'])) {
                    return false;
                }

                return true;
            });

        if (!$survey) return response()->json(null);

        return response()->json([
            'id'        => $survey->id,
            'delay'     => $survey->delay_seconds,
            'questions' => $survey->questions()->orderBy('order')->get(),
        ]);
    }

    public function submit(Request $request, Survey $survey): JsonResponse
    {
        SurveyResponse::create([
            'survey_id'     => $survey->id,
            'user_id'       => auth()->id(),
            'answers'       => $request->input('answers'),
            'trigger_event' => $request->input('event'),
        ]);

        return response()->json(['success' => true]);
    }
}

Frontend: SurveyWidget

// hooks/useSurvey.ts
export function useSurvey(event: string) {
  const [survey, setSurvey] = useState<Survey | null>(null);

  const triggerEvent = useCallback(async () => {
    const data = await fetch(`/api/surveys/for-event?event=${event}`).then(r => r.json());
    if (!data) return;

    // Показуємо із затримкою
    setTimeout(() => setSurvey(data), data.delay * 1000);
  }, [event]);

  return { survey, triggerEvent, dismiss: () => setSurvey(null) };
}

// Використання в компоненті фічі
function FeatureComponent() {
  const { survey, triggerEvent, dismiss } = useSurvey('feature_export_used');

  useEffect(() => {
    // Тригер після успішного експорту
    triggerEvent();
  }, []);

  return (
    <>
      <FeatureUI />
      {survey && <SurveyModal survey={survey} onClose={dismiss} />}
    </>
  );
}

SurveyModal з динамічними типами запитань

function SurveyModal({ survey, onClose }: SurveyModalProps) {
  const [answers, setAnswers] = useState<Record<string, any>>({});
  const [step, setStep]       = useState(0);

  const currentQuestion = survey.questions[step];
  const isLast          = step === survey.questions.length - 1;

  const submit = async () => {
    await fetch(`/api/surveys/${survey.id}/submit`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ answers, event: survey.trigger_event }),
    });
    onClose();
  };

  return (
    <div className="fixed inset-0 bg-black/30 flex items-end sm:items-center justify-center p-4 z-50">
      <div className="bg-white rounded-xl p-6 w-full max-w-md shadow-2xl">
        <div className="flex justify-between mb-4">
          <span className="text-xs text-gray-400">{step + 1} / {survey.questions.length}</span>
          <button onClick={onClose} className="text-gray-400 hover:text-gray-600">✕</button>
        </div>

        <QuestionRenderer question={currentQuestion} value={answers[currentQuestion.id]}
          onChange={val => setAnswers(prev => ({ ...prev, [currentQuestion.id]: val }))} />

        <div className="flex justify-end mt-4">
          <button onClick={isLast ? submit : () => setStep(s => s + 1)}
            className="bg-blue-600 text-white px-4 py-2 rounded-lg text-sm">
            {isLast ? 'Відправити' : 'Далі'}
          </button>
        </div>
      </div>
    </div>
  );
}

Порівняння каналів опитувань: in-app vs email vs popup

Характеристика In-app survey Email-опитування Popup (спливаюче вікно)
Контекстність Повна (прив'язаний до події) Низька (відкривається поза продуктом) Середня (з'являється на сторінці, але не прив'язаний до дії)
Конверсія у відповідь 10-30% 2-5% 5-10%
Вплив на UX Мінімальний (показується після дії, із затримкою) Не впливає Високий (перекриває контент)
Складність інтеграції Середня (потрібен бекенд + віджет) Низька (проста форма) Низька (вставка коду)
Типи даних Тільки вибіркові (цільові події) Випадкова вибірка Всі відвідувачі

In-app survey краще email у 3-6 разів за відгуком, а popup програє за UX.

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

  • Міграції БД та моделі Eloquent
  • REST API з кешуванням через Redis (зниження навантаження на 80%)
  • React-віджет з підтримкою 5 типів запитань
  • Адмін-панель для перегляду відповідей та управління опитуваннями
  • Документація по API та налаштуванню, код у вашому репозиторії
  • 2 тижні безкоштовної підтримки після деплою

Типові помилки при впровадженні in-app survey

Поширені проблеми та їх вирішення 1. **Забагато запитань** — знижує відгук. Оптимум: 2-4 запитання. 2. **Немає cooldown** — користувач бачить опитування щоразу, це дратує. Ставте мінімум 30 днів. 3. **Показ на всіх сторінках** — має бути прив'язаний до конкретної події. 4. **Ігнорування мобільної версії** — віджет повинен коректно працювати на екранах < 375px. 5. **Відсутність обробки помилок** — якщо API недоступний, віджет не повинен ламати інтерфейс.

Початок роботи

За даними Wikipedia, контекстні опитування підвищують відгук на 30-50% порівняно із зовнішніми каналами.

Залиште заявку — ми оцінимо ваш проєкт за один робочий день. Вкажіть стек, кількість користувачів та події, які хочете відстежувати. Система in-app опитувань зазвичай окупається за два тижні завдяки точному зворотному зв'язку та зростанню утримання.

Терміни: від 4 до 10 робочих днів залежно від складності. Гарантії: фіксовані терміни, повна документація, код у вашому репозиторії. Зв'яжіться з нами — ми допоможемо впровадити контекстні опитування, які дійсно підвищують метрики. Отримайте консультацію прямо зараз.

Як налаштувати веб-аналітику: GA4, GTM, Яндекс.Метрика та Amplitude

Ми часто бачимо: конверсія 1.2 %, трафік зростає, а конверсія стоїть. Маркетолог дивиться в Google Analytics і каже: «користувачі йдуть з кроку 2 оформлення замовлення». Розробник відкриває той самий крок — помилок немає, в Sentry тиша. Значить, справа не в JS-базі, а в UX або в кривих даних, які показує аналітика. Аналітика ламається непомітно: подія перестала трекатися після редеплою — ніхто не помітив; GTM-тег стріляє двічі — дані задвоїлися; фільтр GA4 виключає бота, який насправді — реальний трафік з корпоративного проксі. Замовте аудит поточних тегів — ми знайдемо причину за тиждень. Ми маємо понад 5 років досвіду в налаштуванні веб-аналітики для 100+ проєктів — гарантуємо прозорість та достовірність даних.

Після правильного налаштування економія рекламного бюджету може досягати значної суми щомісяця — це реальний кейс інтернет-магазину з 50 000 сесій на день, де дедуплікація purchase повернула 20 % невірно приписаних конверсій.

Чому події GA4 дублюються і як це виправити?

Universal Analytics закрито, його місце зайняла подієва модель GA4. У ній немає фіксованих хітів сторінок і транзакцій — лише події з параметрами. Це гнучкіше, але вимагає правильного дизайну подій.

Автоматичні події GA4 збирає сам: page_view, scroll, click, session_start. Рекомендовані події потрібно реалізувати самостійно: purchase, add_to_cart, begin_checkout, view_item. Google очікує конкретну схему параметрів — якщо передати product_id замість item_id, дані потрапляють в GA4, але не в стандартні звіти e-commerce. Кастомні події для специфіки проєкту: filter_applied, video_progress, form_step_completed. Кастомні параметри необхідно зареєструвати в GA4 Admin → Custom definitions, інакше вони не будуть доступні у звітах.

Часта помилка — подія purchase з дублями. Причина: тег спрацьовує на сторінці /thank-you, користувач оновлює сторінку — другий purchase іде в GA4. Рішення: на бекенді генеруємо унікальний transaction_id і передаємо в подію. GA4 de-duplicates по ньому — перевіряйте через DebugView. Правильна атрибуція економить до 20 % рекламного бюджету, який раніше йшов на невірно приписані конверсії.

Як налаштувати data layer, щоб не втратити дані?

GTM — інструмент для керування тегами без деплою коду. Але «без коду» не означає «без архітектури». Data Layer — основа всього. Передаємо дані з застосунку в GTM через dataLayer.push(). Структура: event + контекстні дані. Для e-commerce: перед відкриттям сторінки продукту — push з даними товару. GTM-тег читає з dataLayer, не з DOM.

window.dataLayer = window.dataLayer || [];
dataLayer.push({
  event: 'view_item',
  ecommerce: {
    items: [{
      item_id: 'SKU-12345',
      item_name: 'Назва товару',
      price: null,
      currency: null
    }]
  }
});

Погана практика: GTM-тег парсить DOM — шукає ціну в span.price, назву в h1. Це ламається при будь-якій зміні верстки. Хороша практика: завжди dataLayer. Використовуємо Preview Mode для налагодження та GTM Server-Side для чутливих даних — відправка з сервера, не з браузера, обходить блокувальники реклами, не втрачає дані. Server-side підхід у 2-3 рази надійніший за client-side за показником втрати подій через розширення браузера.

Як Яндекс.Метрика доповнює веб-аналітику?

Для російської аудиторії Метрика обов'язкова — особливо Вебвізор. Запис сесії користувача, який кинув кошик, часто дає відповідь швидше, ніж тиждень аналізу воронки. Цілі в Метриці: подієві (через ym(COUNTER_ID, 'reachGoal', 'GOAL_NAME')) або автоматичні (клік по кнопці, відвідування сторінки). Зв'язка з CRM через Метрика Плюс — передача офлайн-конверсій. Наш досвід: у 8 з 10 проєктів після налаштування Метрики знаходили приховані баги в UX, які не показували інші системи.

Що дає product analytics в Amplitude?

Amplitude — продуктовий інструмент, на відміну від маркетингових GA4 та Метрики. Він заточений під аналіз поведінки користувачів всередині продукту: воронки, ретеншн, user paths. Amplitude підходить для SaaS-продуктів, мобільних застосунків та будь-яких сервісів із зареєстрованими користувачами, де важливо зрозуміти, як проходять онбординг, на якому кроці йдуть, які фічі використовують частіше. Ключові концепції: identify (пов'язати анонімного користувача з userId після авторизації), group (акаунт у B2B SaaS), когорти для утримання. Amplitude Chart — воронка кроків за останні 30 днів з розбивкою за джерелом.

Моніторинг якості даних

Аналітика без моніторингу — чорна скринька. Налаштовуємо:

  • GA4 Realtime — перевіряємо після кожного деплою, що ключові події приходять
  • Alerting в GA4 — аномалія в кількості подій purchase (різке падіння = щось зламалося)
  • GTM Preview в staging-оточенні перед продакшеном
  • Ручні тести воронок раз на тиждень — просто пройти шлях покупця і перевірити, що все трекається

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

Що перевіряємо після кожного деплою

  • Чи всі рекомендовані події присутні в DebugView
  • Чи немає задвоєнь (рахуємо кількість purchase на 100 сесій)
  • Чи не змінилася структура dataLayer після оновлення фронтенду

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

Компонент Опис
Аудит поточних тегів Перевірка існуючих GTM-тегів, dataLayer, дублів та помилок
Дизайн подієвої схеми Документація: список подій, параметри, тригери
Налаштування GA4 + GTM Створення конфігурації, тегів, Custom definitions
Яндекс.Метрика Встановлення лічильника, створення цілей, налаштування Вебвізора
Amplitude (опціонально) Налаштування клієнтського та серверного SDK, когорти
QA та моніторинг Тестування в Preview Mode, Alerting
Навчання та передача Доступи, інструкція з додавання нових подій, консоль

Процес та терміни

  1. Аудит поточних тегів та даних (2 дні)
  2. Дизайн подієвої схеми (2 дні)
  3. Розробка Data Layer та налаштування тегів (3–5 днів)
  4. QA в Preview Mode та на staging (2 дні)
  5. Деплой та налаштування дашбордів (1 день)
Сценарій Термін
Базове налаштування GA4 + GTM 1 тиждень
Повний e-commerce tracking + Метрика 2–3 тижні
Server-side GTM + Amplitude 3–5 тижнів

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