React Product Filter Development for 1C-Bitrix
The standard bitrix:catalog.smart.filter component works through server-side rendering with a full page reload or AJAX HTML replacement. Every filter change triggers an HTTP request, a server render, and a DOM swap. On a catalog of 50,000 SKUs with 30+ attributes, response time is 800–2,000 ms, and that is before accounting for frontend rendering time.
A React filter changes the approach: the UI updates instantly, the server request is sent with a 300–500 ms debounce, and results update in the background. The user sees a response immediately rather than waiting for a server round-trip.
React Filter Architecture
The filter consists of three independent parts: the filter component (checkboxes, ranges, sorting), the catalog component (product list, pagination), and URL synchronization (filter state is reflected in the URL for sharing and SEO).
Filter state is synchronized with the URL via useSearchParams from React Router or the native URLSearchParams. This is critical: users must be able to copy the URL with applied filters.
// Hook for synchronizing filter state with the URL
function useFilterState(initialFilters: FilterState) {
const [searchParams, setSearchParams] = useSearchParams();
const filters = useMemo(() => {
return parseFiltersFromParams(searchParams, initialFilters);
}, [searchParams]);
const setFilters = useCallback((newFilters: Partial<FilterState>) => {
const params = buildParamsFromFilters({ ...filters, ...newFilters });
setSearchParams(params, { replace: true }); // replace, not push — avoid polluting history
}, [filters, setSearchParams]);
return [filters, setFilters] as const;
}
Bitrix-Side API
The server endpoint accepts filter parameters and returns products along with facet counts (how many products exist for each attribute value).
public function getProductsAction(array $filter = [], int $page = 1): array
{
// Build Bitrix filter from request parameters
$bxFilter = $this->buildBitrixFilter($filter);
// Products
$result = \CIBlockElement::GetList(
['SORT' => 'ASC'],
$bxFilter,
false,
['nPageSize' => 24, 'iNumPage' => $page],
['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL',
'CATALOG_PRICE_1', 'PROPERTY_BRAND', 'PROPERTY_COLOR']
);
$products = [];
while ($product = $result->GetNextElement()) {
$fields = $product->GetFields();
$props = $product->GetProperties();
$products[] = $this->formatProduct($fields, $props);
}
// Facet counts for updating filter options
$facets = $this->getFacets($bxFilter, $filter);
return [
'products' => $products,
'total' => $result->SelectedRowsCount(),
'facets' => $facets,
];
}
For facet counts, use \Bitrix\Iblock\Component\Tools or direct queries against b_iblock_element_property tables with GROUP BY — the right choice depends on load and catalog size.
Filter Types and Their Implementation
Checkbox groups (brand, color, material) — the most common type. Values come from iblock properties. On the frontend — a list with internal search when there are many values (20+).
Price range — a dual-handle price slider. Use the rc-slider library or a native CSS custom properties implementation. The request is sent with a 500 ms debounce — otherwise every pixel of slider movement triggers a request.
Rating — star rating as a filter (from N stars).
Size grid — toggle buttons. Important: availability of a specific size depends on stock, so unavailable values should be grayed out rather than simply absent.
Case Study: Fashion Marketplace Filter
Multi-vendor catalog, ~180,000 SKUs, 45 attributes. Requirements: the filter must respond instantly, support multilevel dependencies (category selection changes available filters), and reflect state in the URL for SEO.
Bitrix's standard SmartFilter gave response times of 1.4–2.1 s; facet counts were recalculated on every request and became the bottleneck.
Solution:
-
Facets moved to a separate cacheable endpoint. Counts are recalculated asynchronously via a background job (
\Bitrix\Main\Application::getInstance()->addBackgroundJob()) after stock changes, not in real time on each request. -
Optimistic UI on the frontend: the checkbox is checked instantly, the product count updates with a slight delay. The user does not wait for server confirmation for the visual response.
-
Mobile filter — a separate component as a bottom sheet (slides up from below). Desktop — sidebar. Switching via CSS media query + React context.
-
Empty results — not a white screen, but a block suggesting the user broaden the filter (remove one attribute). React analyzes which last-selected parameter yields 0 results and suggests removing it.
| Characteristic | SmartFilter | React Filter |
|---|---|---|
| Response time on checkbox click | 1400–2100 ms | 0 ms (UI), 280–400 ms (data) |
| Count updates | On every request | From cache, background refresh |
| Deep link sharing | Partial | Full (all parameters in URL) |
| Mobile UX | Separate page | Bottom sheet without navigation |
SEO and SSR
A React filter creates an SEO problem: search engines do not always execute JavaScript. There are several solutions.
SSR through a dedicated renderer — heavy, requires a Node.js infrastructure alongside Bitrix.
Prerendering — the SPA is rendered via headless Chrome during crawling. Implemented via Prerender.io or an nginx configuration that detects a bot User-Agent and proxies the request to the renderer.
Hybrid approach (recommended): pages with SEO-valuable filter combinations (category + brand) have static PHP URLs rendered by Bitrix. The React filter works on top of these pages but reads its initial state from the PHP template (passed via window.__INITIAL_STATE__). Bots see HTML, users get interactive React.
Performance Optimization
Infinite scroll vs. pagination: on mobile — infinite scroll via IntersectionObserver; on desktop — pagination (better for SEO and bookmarking a specific page).
List virtualization for large result sets (react-virtual or @tanstack/react-virtual) — rendering 200+ cards without virtualization makes the DOM heavy.
React Query for caching: navigating between catalog pages does not trigger a new request if the data is still fresh (staleTime: 30_000).
Scope of Work
- Catalog analysis: attributes, filter types, data volume
- API controller: filtering, pagination, facets, caching
- React development: filter components, catalog, URL synchronization
- SEO strategy: prerendering or hybrid approach
- Performance testing under load
Timeline: basic filter (checkboxes + price range) — 2–3 weeks. Full-featured with facets, mobile UX, and SEO strategy — 5–8 weeks.







