React Search Development for 1C-Bitrix
The standard Bitrix search works through the search module with a morphological index in the b_search_content and b_search_stem tables. The standard bitrix:search.title component redirects to a results page that is server-rendered. For a simple informational site this is acceptable. For an online store with live search (instant search, suggestions as you type, filtering directly in the dropdown) — the standard approach is architecturally unsuitable.
A React search means a dedicated index (Elasticsearch/OpenSearch or custom SQL) combined with a React interface featuring debounce, categorized results, and click analytics.
Choosing a Search Engine
Elasticsearch / OpenSearch — justified when the volume exceeds 100,000 documents and you need full-text search with morphology, boost ranking, and synonyms. Requires a dedicated server.
Typesense — a simpler alternative with good performance and easier administration. Suitable for mid-size catalogs.
Meilisearch — fast to get started, fuzzy search out of the box, solid documentation. Suitable for catalogs up to 500,000 products.
Custom SQL (Bitrix) — FULLTEXT INDEX in MySQL or tsvector in PostgreSQL. Works without additional infrastructure; sufficient for catalogs up to 50,000 SKUs.
For most mid-size online stores (up to 100,000 SKUs) the recommendation is Meilisearch or custom PostgreSQL full-text. Elasticsearch only if the infrastructure already exists or complex search analytics are required.
Indexing Bitrix Products
Regardless of the chosen engine, you need data synchronization between Bitrix and the search index.
// Event handler for product updates
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'catalog', 'OnProductUpdate',
function(\Bitrix\Main\Event $event) {
$productId = $event->getParameter('id');
\Local\Search\IndexQueue::add($productId, 'update');
}
);
// Indexer (runs via 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);
}
}
Synchronization via a queue (\Bitrix\Main\Application::getInstance()->addBackgroundJob()) — changes are processed asynchronously without slowing down the main request.
React Search Component
Instant search with debounce is the foundation of the UX. Minimum delay for a live-response feel: 200–300 ms.
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="Search products..."
/>
{debouncedQuery.length >= 2 && (
<SearchDropdown results={data} isLoading={isLoading} />
)}
</div>
);
}
The search dropdown is divided into categories: "Products," "Categories," "Brands," "Articles." Categorization is done server-side or client-side from a single response.
Case Study: Building Supplies Hypermarket Search
Building materials store, 85,000 SKUs, 12,000 unique search queries per day. Problem: the standard Bitrix search did not find products with typos ("shpatlofka" instead of "shpatlevka"), did not support search by article number, and response time was 800–1,200 ms.
Meilisearch was chosen (single server, synchronization via Bitrix queue).
Implementation:
The index includes: name, description, brand, article number, synonyms (a separate Bitrix table with "query → correct term" pairs). Fuzzy search with typoTolerance — Meilisearch handles typos automatically.
The React dropdown has 4 sections: top 4 products, categories (if the query matches a category name), brands, and "View all results." The dropdown height is fixed (max 480 px) with an internal scrollable list.
Click analytics: when a search result is clicked, an event is sent to GA4 and to a local local_search_analytics table — what was searched, what was found, what was clicked. Data is used for manual boost ranking adjustments (popular products are ranked higher).
| Metric | Before | After |
|---|---|---|
| Search response time | 800–1200 ms | 35–80 ms |
| Zero-results rate | 18% | 4% |
| Search CTR | 34% | 61% |
| Results by article number | No | Yes |
Full Results Page
For the full results page (/search/?q=shpatlevka) — a React application with a sidebar filter (the same components as in the catalog), sorting, and pagination. The URL is synchronized with all parameters.
Highlighting — matching terms are highlighted in search results. Meilisearch returns a _formatted field with <em> tags that are styled in React.
Search Analytics
Search analytics is an underutilized tool. Queries with zero results directly indicate products that are wanted but not stocked. Queries with low CTR point to expectation mismatches.
Minimum metric set: top queries, zero-results queries, CTR per query, conversion from search. All of this is built on GA4 events plus a custom analytics table.
Scope of Work
- Selecting a search engine appropriate for the volume and budget
- Index setup, synchronization with the Bitrix catalog
- React development: instant search, dropdown, full results page
- Configuring synonyms, stop words, boost ranking
- Analytics: click tracking, zero-results monitoring
Timeline: instant search with dropdown — 2–3 weeks. Full results page + analytics — another 2–3 weeks.







