Разработка поиска на React для 1С-Битрикс
Стандартный поиск в Битрикс работает через модуль search с морфологическим индексом в таблицах b_search_content и b_search_stem. Штатный компонент bitrix:search.title редиректит на страницу результатов, которая рендерится сервером. Для простого информационного сайта — нормально. Для интернет-магазина с живым поиском (instant search, подсказки по мере ввода, фильтрация прямо в дропдауне) — штатный подход неприменим по архитектурным причинам.
React-поиск — это собственный индекс (Elasticsearch/OpenSearch или кастомный SQL) + React-интерфейс с debounce, категоризацией результатов, аналитикой кликов.
Выбор поискового движка
Elasticsearch / OpenSearch — оправданы при объёме >100 000 документов, необходимости full-text search с морфологией, буст-ранжировании, синонимах. Требуют отдельного сервера.
Typesense — более простая альтернатива с хорошей производительностью, проще в администрировании. Подходит для среднего каталога.
Meilisearch — быстрый старт, fuzzy search из коробки, хорошая документация. Для каталогов до 500 000 товаров.
Кастомный SQL (Битрикс) — FULLTEXT INDEX в MySQL или tsvector в PostgreSQL. Работает без дополнительной инфраструктуры, достаточно для каталогов до 50 000 SKU.
Для большинства средних интернет-магазинов (до 100 000 SKU) рекомендую Meilisearch или кастомный PostgreSQL full-text. Elasticsearch — только если уже есть инфраструктура или требуется сложная аналитика поиска.
Индексация товаров Битрикс
Независимо от выбранного движка, нужна синхронизация данных между Битрикс и поисковым индексом.
// Обработчик события изменения товара
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'catalog', 'OnProductUpdate',
function(\Bitrix\Main\Event $event) {
$productId = $event->getParameter('id');
\Local\Search\IndexQueue::add($productId, 'update');
}
);
// Индексатор (выполняется через cron)
class ProductIndexer
{
public function indexProduct(int $productId): void
{
$product = \CIBlockElement::GetById($productId)->GetNext();
$prices = \CCatalogProduct::GetOptimalPrice($productId, 1, [], 'N', [], SITE_ID);
$props = $this->getProductProperties($productId);
$document = [
'id' => $productId,
'name' => $product['NAME'],
'description' => strip_tags($product['DETAIL_TEXT']),
'brand' => $props['BRAND']['VALUE'],
'price' => $prices['RESULT_PRICE']['DISCOUNT_PRICE'],
'in_stock' => $props['QUANTITY']['VALUE'] > 0,
'category' => $this->getCategoryPath($product['IBLOCK_SECTION_ID']),
];
$this->searchEngine->upsertDocument($document);
}
}
Синхронизация через очередь (\Bitrix\Main\Application::getInstance()->addBackgroundJob()) — изменения обрабатываются асинхронно, не замедляя основной поток.
React-компонент поиска
Instant search с debounce — основа UX. Минимальное время задержки для ощущения живого отклика: 200–300 мс.
function SearchBox() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 250);
const { data, isLoading } = useQuery({
queryKey: ['search', debouncedQuery],
queryFn: () => searchProducts(debouncedQuery),
enabled: debouncedQuery.length >= 2,
staleTime: 10_000,
});
return (
<div className="search-wrapper">
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Поиск товаров..."
/>
{debouncedQuery.length >= 2 && (
<SearchDropdown results={data} isLoading={isLoading} />
)}
</div>
);
}
Дропдаун поиска разбивается по категориям: «Товары», «Категории», «Бренды», «Статьи». Категоризация делается на сервере или на фронте из единого ответа.
Кейс: поиск для строительного гипермаркета
Магазин стройматериалов, 85 000 SKU, 12 000 уникальных поисковых запросов в сутки. Проблема: штатный поиск Битрикс не находил товары при опечатках («шпатлёфка» вместо «шпатлёвка»), не поддерживал поиск по артикулу, время ответа — 800–1200 мс.
Выбрали Meilisearch (один сервер, синхронизация через очередь Битрикс).
Реализация:
Индекс включает: название, описание, бренд, артикул, синонимы (отдельная таблица в Битрикс с парами «запрос → правильный термин»). Fuzzy search с typoTolerance — Meilisearch автоматически обрабатывает опечатки.
В React-дропдауне — 4 секции: топ-4 товара, категории (если запрос совпадает с названием категории), бренды, «Смотреть все результаты». Высота дропдауна фиксирована (max 480px), внутри прокручиваемый список.
Аналитика кликов: при клике на результат поиска отправляется событие в GA4 и в собственную таблицу local_search_analytics — что искали, что нашли, что кликнули. Данные используются для ручной настройки буст-ранжирования (популярные товары поднимаются выше).
| Метрика | До | После |
|---|---|---|
| Время ответа поиска | 800–1200 мс | 35–80 мс |
| Zero-results rate | 18% | 4% |
| CTR из поиска | 34% | 61% |
| Нахождение по артикулу | Нет | Да |
Полная страница результатов
Для полной страницы результатов (/search/?q=шпатлёвка) — React-приложение с боковым фильтром (те же компоненты, что и в каталоге), сортировкой, пагинацией. URL синхронизируется со всеми параметрами.
Highlighting — подсветка совпадений в результатах поиска. Meilisearch возвращает _formatted поле с тегами <em>, которые стилизуются в React.
Аналитика поиска
Поисковая аналитика — недооценённый инструмент. Запросы с нулевыми результатами прямо указывают на товары, которых нет, но которые ищут. Запросы с низким CTR — на несоответствие ожиданиям.
Минимальный набор метрик: top запросы, zero-results запросы, CTR по запросам, конверсия из поиска. Всё это строится на основе событий в GA4 + собственной аналитической таблице.
Состав работ
- Выбор поискового движка под объём и бюджет
- Настройка индекса, синхронизация с каталогом Битрикс
- Разработка React: instant search, дропдаун, полная страница результатов
- Настройка синонимов, стоп-слов, boost-ранжирования
- Аналитика: click tracking, zero-results мониторинг
Сроки: instant search с дропдауном — 2–3 недели. Полная страница результатов + аналитика — ещё 2–3 недели.







