@letar/forms

Аналитика форм

Встроенная аналитика на уровне полей — отслеживание отказов, времени на поле и конверсии

Полный пример

Полный sandbox-пример, прочитанный напрямую из исходников form-develop-app на сборке — переключите фреймворк/скин, чтобы увидеть useFormAnalytics, подключённый к реальной форме. Аналога для shadcn/Vue/Angular пока нет — эти вкладки показаны как disabled.

apps/form-develop-app/src/app/analytics-demo/page.tsx
'use client'

import { Box, Code, Heading, Text, VStack } from '@chakra-ui/react'
import { DemoPageLayout } from '../_components'

export default function AnalyticsDemoPage() {
  return (
    <DemoPageLayout
      title="Form.Analytics"
      description="Встроенная field-level аналитика форм — drop-off, время на полях, completion rate"
    >
      <VStack gap={8} align="stretch">
        <Box>
          <Heading size="md" mb={3}>
            Использование
          </Heading>
          <Code display="block" whiteSpace="pre" fontSize="xs" p={4} borderRadius="md">
            {`import { AnalyticsPanel, createUmamiAdapter, useFormAnalytics } from '@letar/forms'

const analytics = useFormAnalytics({
  formId: 'contact-form',
  adapters: [createUmamiAdapter()],
  onAbandon: (lastField, filled, total) => {
    console.log(\`Drop-off на поле: \${lastField} (\${filled}/\${total})\`)
  },
})

<Form schema={ContactSchema} onSubmit={save}>
  <Form.Field.String name="name" />
  <Form.Field.String name="email" />
  <Form.Field.Textarea name="message" />
  <Form.Button.Submit>Отправить</Form.Button.Submit>
  
  <AnalyticsPanel analytics={analytics} position="bottom-right" />
</Form>`}
          </Code>
        </Box>

        <Box>
          <Heading size="md" mb={3}>
            Адаптеры
          </Heading>
          <Code display="block" whiteSpace="pre" fontSize="xs" p={4} borderRadius="md">
            {`// Umami (наш стек)
createUmamiAdapter()

// Яндекс Метрика (для РФ рынка)
createYandexMetrikaAdapter(12345) // counter ID → goals: form_*_abandon, form_*_complete

// Google Analytics 4
createGtagAdapter() // gtag events: form_field_interaction, form_abandon, form_complete

// PostHog
createPostHogAdapter() // posthog.capture: form_field_focus, form_form_complete

// Кастомный
const myAdapter = { name: 'custom', track: (event, formId) => fetch('/api/analytics', { body: JSON.stringify({ event, formId }) }) }`}
          </Code>
        </Box>

        <Box>
          <Heading size="md" mb={3}>
            Что трекается
          </Heading>
          <VStack align="start" gap={2} fontSize="sm">
            <Text>
              <strong>field_focus</strong> — количество фокусов на каждом поле
            </Text>
            <Text>
              <strong>field_blur</strong> — время проведённое на поле (мс)
            </Text>
            <Text>
              <strong>field_error</strong> — ошибки валидации по полям
            </Text>
            <Text>
              <strong>field_correction</strong> — возврат к полю после blur (исправления)
            </Text>
            <Text>
              <strong>form_abandon</strong> — последнее поле, заполненных полей, общее время
            </Text>
            <Text>
              <strong>form_complete</strong> — общее время, время по каждому полю
            </Text>
          </VStack>
        </Box>

        <Box>
          <Heading size="md" mb={3}>
            Статистика
          </Heading>
          <VStack align="start" gap={2} fontSize="sm">
            <Text>67% форм бросают незавершёнными (Zuko, 2025)</Text>
            <Text>Поле пароля — рекордсмен drop-off (10.5%)</Text>
            <Text>Оптимизация формы даёт +30-50% к completion rate</Text>
            <Text>Desktop: 47% completion vs Mobile: 42%</Text>
          </VStack>
        </Box>
      </VStack>
    </DemoPageLayout>
  )
}

Обзор

67% форм бросают до завершения. Поле пароля — 10,5% отказов. useFormAnalytics() даёт видимость того, где именно пользователи испытывают трудности.

import { AnalyticsPanel, createUmamiAdapter, useFormAnalytics } from '@letar/forms'

function ContactForm() {
  const analytics = useFormAnalytics({
    formId: 'contact-form',
    adapters: [createUmamiAdapter()],
  })

  return (
    <Form schema={ContactSchema} onSubmit={save}>
      <Form.Field.String name="name" />
      <Form.Field.String name="email" />
      <Form.Field.Textarea name="message" />
      <Form.Button.Submit>Отправить</Form.Button.Submit>

      {/* Панель только для разработки */}
      {process.env.NODE_ENV === 'development' && <AnalyticsPanel analytics={analytics} />}
    </Form>
  )
}

Что отслеживается

МетрикаОписание
ФокусыСколько раз каждое поле получало фокус
Время на полеМиллисекунды, проведённые на каждом поле
ОшибкиОшибки валидации по полям
ИсправленияСколько раз пользователь возвращался к полю
КонверсияПроцент заполненных полей (0-100%)
ОтказПоследнее поле, число заполненных, общее время при beforeunload
ЗавершениеОбщее время и раскладка по полям при submit

Адаптеры

Umami

import { createUmamiAdapter } from '@letar/forms/analytics'
const adapter = createUmamiAdapter()

Яндекс Метрика

import { createYandexMetrikaAdapter } from '@letar/forms/analytics'
const adapter = createYandexMetrikaAdapter(12345) // ID счётчика

Отправляет цели: form_{formId}_field_focus, form_{formId}_abandon, form_{formId}_complete.

Google Analytics 4

import { createGtagAdapter } from '@letar/forms/analytics'
const adapter = createGtagAdapter()

PostHog

import { createPostHogAdapter } from '@letar/forms/analytics'
const adapter = createPostHogAdapter()

Свой адаптер

const myAdapter: AnalyticsAdapter = {
  name: 'my-analytics',
  track(event, formId) {
    fetch('/api/analytics', {
      method: 'POST',
      body: JSON.stringify({ event, formId }),
    })
  },
}

API: useFormAnalytics(config?)

const analytics = useFormAnalytics({
  enabled: true,
  formId: 'contact-form',
  adapters: [createUmamiAdapter()],
  trackCorrections: true,
  onFieldFocus: (field, timestamp) => {},
  onFieldBlur: (field, timestamp, timeSpentMs) => {},
  onFieldError: (field, error) => {},
  onAbandon: (lastField, filledFields, totalFields) => {},
  onComplete: (totalTimeMs, fieldTimes) => {},
})

Возвращает:

ПолеТипОписание
fieldAnalyticsMap<string, FieldAnalytics>Метрики по полям
completionRatenumber0-100%
lastFocusedFieldstring | nullПоследнее поле в фокусе
totalTimeMsnumberВремя с момента монтирования
totalErrorsnumberОбщее число ошибок
trackAbandon()() => voidПринудительно отправить abandon
trackComplete()() => voidПринудительно отправить complete
reset()() => voidСбросить все метрики

AnalyticsPanel

Плавающая dev-панель с метриками в реальном времени:

<AnalyticsPanel
  analytics={analytics}
  position="bottom-right" // bottom-right | bottom-left | top-right | top-left
/>

Импорт

import { AnalyticsPanel, useFormAnalytics } from '@letar/forms'
import { createUmamiAdapter } from '@letar/forms/analytics'

On this page