Розробка пошуку на 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 — на невідповідність очікуванням.
Мінімальний набір метрик: топ запити, zero-results запити, CTR за запитами, конверсія з пошуку. Все це будується на основі подій у GA4 + власній аналітичній таблиці.
Склад робіт
- Вибір пошукового рушія під обсяг і бюджет
- Налаштування індексу, синхронізація з каталогом Бітрікс
- Розробка React: instant search, дропдаун, повна сторінка результатів
- Налаштування синонімів, стоп-слів, boost-ранжування
- Аналітика: click tracking, zero-results моніторинг
Терміни: instant search із дропдауном — 2–3 тижні. Повна сторінка результатів + аналітика — ще 2–3 тижні.







