Каталог — самая нагруженная часть интернет-магазина: фильтрация, сортировка, пагинация, быстрый просмотр, добавление в корзину. Стандартный компонент bitrix:catalog при каждом изменении фильтра делает полный перезапрос страницы с серверным рендерингом, что занимает 1.5–3 секунды. Представьте каталог с 50 000 товаров — каждый клик фильтра означает потерю времени и нервов покупателя. Мы разрабатываем React-каталог, который даёт мгновенную реакцию на действия пользователя: страница загружается за 200–400 мс, смена фильтра занимает до 300 мс. Разница в UX ощутима сразу: конверсия в корзину растёт на 15–30%. Наш опыт — более 5 лет и 50+ проектов на стыке Битрикса и React.
Почему React вместо стандартного каталога?
Браузерный рендеринг избавляет от полных перезапросов. Каждое изменение фильтра — это асинхронный запрос к REST API, а не перезагрузка страницы. Бэкенд использует тегированное кэширование: при изменении товара сбрасывается только кэш, связанный с этим товаром, а не весь каталог. Результат: плавные анимации, сохранение состояния фильтров в URL, возможность поделиться ссылкой на конкретную выдачу. Пользователи проводят больше времени в каталоге, конверсия увеличивается.
Как работает API для каталога?
Бэкенд на PHP отдаёт данные каталога в JSON. Базовый контроллер обрабатывает запросы с фильтрами, сортировкой и пагинацией:
// /local/ajax/api.php — обработчик catalog.list
case 'catalog.list':
CModule::IncludeModule('iblock');
CModule::IncludeModule('catalog');
$filter = [
'IBLOCK_ID' => CATALOG_IBLOCK_ID,
'ACTIVE' => 'Y',
'SECTION_ID'=> (int)$_GET['section_id'],
];
// Ценовые фильтры
if (!empty($_GET['price_from'])) {
$filter['>=CATALOG_PRICE_1'] = (float)$_GET['price_from'];
}
if (!empty($_GET['price_to'])) {
$filter['<=CATALOG_PRICE_1'] = (float)$_GET['price_to'];
}
// Фильтр по свойствам
if (!empty($_GET['props'])) {
$props = json_decode($_GET['props'], true);
foreach ($props as $propCode => $values) {
$filter['PROPERTY_' . $propCode] = $values;
}
}
$sort = match($_GET['sort'] ?? 'default') {
'price_asc' => ['CATALOG_PRICE_1' => 'ASC'],
'price_desc' => ['CATALOG_PRICE_1' => 'DESC'],
'new' => ['DATE_CREATE' => 'DESC'],
default => ['SORT' => 'ASC'],
};
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = 24;
$res = CIBlockElement::GetList(
$sort, $filter, false,
['iNumPage' => $page, 'nPageSize' => $limit],
['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL',
'PROPERTY_ARTICLE', 'CATALOG_PRICE_1']
);
$items = [];
while ($el = $res->GetNext()) {
$items[] = [
'id' => $el['ID'],
'name' => $el['NAME'],
'slug' => $el['CODE'],
'price' => (float)$el['CATALOG_PRICE_1'],
'image' => CFile::GetPath($el['PREVIEW_PICTURE']),
'url' => $el['DETAIL_PAGE_URL'],
];
}
echo json_encode([
'result' => $items,
'total' => $res->SelectedRowsCount(),
'pages' => ceil($res->SelectedRowsCount() / $limit),
]);
break;
React-компонент каталога с фильтрами
На фронтенде используем хук useSearchParams для синхронизации фильтров с URL. Каждый фильтр — это параметр строки запроса, поэтому ссылку на конкретную выдачу можно скопировать и отправить коллеге.
// CatalogPage.tsx
import { useState, useCallback } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useSearchParams } from 'react-router-dom';
interface CatalogFilters {
priceFrom?: number;
priceTo?: number;
props: Record<string, string[]>;
sort: string;
page: number;
}
export function CatalogPage({ sectionId }: { sectionId: number }) {
const [searchParams, setSearchParams] = useSearchParams();
const filters: CatalogFilters = {
priceFrom: searchParams.get('price_from') ? Number(searchParams.get('price_from')) : undefined,
priceTo: searchParams.get('price_to') ? Number(searchParams.get('price_to')) : undefined,
props: JSON.parse(searchParams.get('props') || '{}'),
sort: searchParams.get('sort') || 'default',
page: Number(searchParams.get('page') || 1),
};
const { data, isLoading } = useQuery({
queryKey: ['catalog', sectionId, filters],
queryFn: () => fetchCatalog(sectionId, filters),
keepPreviousData: true,
});
const updateFilter = useCallback((key: string, value: string | null) => {
setSearchParams(prev => {
if (value) prev.set(key, value);
else prev.delete(key);
prev.delete('page');
return prev;
});
}, [setSearchParams]);
return (
<div className="catalog-layout">
<CatalogFilters
filters={filters}
onFilterChange={updateFilter}
/>
<div className="catalog-main">
<CatalogToolbar
total={data?.total}
sort={filters.sort}
onSortChange={v => updateFilter('sort', v)}
/>
{isLoading ? (
<ProductGrid items={Array(24).fill(null)} skeleton />
) : (
<ProductGrid items={data?.items || []} />
)}
<Pagination
current={filters.page}
total={data?.pages || 1}
onChange={p => updateFilter('page', String(p))}
/>
</div>
</div>
);
}
Фильтры синхронизируются с URL через useSearchParams — это позволяет поделиться ссылкой на конкретную фильтрацию и корректно работать с кнопкой «назад» в браузере.
Что даёт умный фильтр?
Стандартный компонент bitrix:catalog.smart.filter генерирует HTML, непригодный для React. Для кастомного фильтра нужен API умного фильтра:
// catalog.filter.get — доступные значения фильтров для текущего раздела
case 'catalog.filter.get':
// Получаем доступные свойства и их значения
// с учётом текущих выбранных фильтров (для зависимых фильтров)
$availableProps = getSmartFilterProps(
CATALOG_IBLOCK_ID,
(int)$_GET['section_id'],
json_decode($_GET['selected'] ?? '{}', true)
);
echo json_encode(['result' => $availableProps]);
break;
Зависимые фильтры (когда выбор одного значения сужает доступные значения другого) — сложная задача. Реализуется через повторный запрос к API при изменении любого фильтра с передачей текущего выбора. Такой подход обеспечивает поведение, аналогичное стандартному умному фильтру, но в SPA-стиле. Дополнительно мы используем HL-блоки для хранения пользовательских предпочтений, что ускоряет повторные визиты.
Когда стоит переходить на React-каталог?
Если ваш каталог содержит более 10 000 товаров, имеет сложные фильтры с зависимостями или вы замечаете падение конверсии на мобильных устройствах — React-каталог решит эти проблемы. Скорость работы напрямую влияет на выручку: каждая секунда задержки снижает конверсию на 7%. Full-stack подход (PHP + React) даёт гибкость при интеграции с 1С через CommerceML.
Оптимизация производительности
| Метод | Серверный рендеринг | React-каталог |
|---|---|---|
| Первая загрузка | 1.5–3 с | 0.2–0.4 с |
| Смена фильтра | 1.5–3 с (релоад) | 0.1–0.3 с |
| Добавление в корзину | 0.5–1 с | 0.1–0.2 с |
| Сохранение состояния | нет | да |
Виртуализация списка. При показе 100+ товаров используйте @tanstack/react-virtual — рендерится только видимая область. Это снижает потребление памяти и ускоряет скролл.
import { useVirtual } from '@tanstack/react-virtual';
const rowVirtualizer = useVirtual({
count: items.length,
parentRef: containerRef,
estimateSize: () => 350,
});
Prefetch следующей страницы. При скролле к последней видимой строке предзагружайте следующую страницу:
useEffect(() => {
if (isNearEnd && data?.pages > filters.page) {
queryClient.prefetchQuery(
['catalog', sectionId, { ...filters, page: filters.page + 1 }],
() => fetchCatalog(sectionId, { ...filters, page: filters.page + 1 })
);
}
}, [isNearEnd]);
Изображения. Lazy loading через loading="lazy" + современные форматы (WebP через Битрикс \Bitrix\Main\Web\Uri + конвертер или внешний CDN). Для skeleton-заглушек при загрузке используйте CSS-анимацию — дешевле, чем JavaScript-анимации.
Этапы разработки React-каталога
Чек-лист внедрения:
- Анализ инфоблоков и свойств, проектирование API.
- Разработка API-контроллеров (список, фильтры, пагинация).
- Создание React-компонентов (фильтр, сетка, пагинация, скелетоны).
- Интеграция с торговым каталогом и обменом с 1С через CommerceML.
- Настройка тегированного кэширования и HTTP-кэша.
- Тестирование на реальных данных, замеры производительности.
- Подготовка документации и обучение команды заказчика.
Процесс включает аналитику, проектирование API, разработку фронтенда и бэкенда, интеграцию с 1С и тестирование. Каждый этап завершается демонстрацией заказчику.
| Этап | Длительность | Результат |
|---|---|---|
| Анализ | 3–5 дней | Техническое задание и прототип API |
| Разработка API | 5–10 дней | Готовые эндпоинты |
| Фронтенд | 7–14 дней | Рабочий каталог с фильтрами |
| Интеграция | 3–5 дней | Полная совместимость с 1С |
| Тестирование | 2–4 дня | Отчёт о производительности |
Что входит в работу
- API-контроллеры для списка товаров, фильтров, пагинации.
- React-компоненты: фильтр, сетка товаров, пагинация, скелетоны.
- Интеграция с существующими инфоблоками, торговым каталогом, 1С.
- Настройка кэширования (тегированное кэширование, HTTP-кэш).
- Документация по API и архитектуре.
- Обучение ваших разработчиков (до 2 часов).
Получите бесплатную консультацию по вашему проекту — мы оценим объём работ и предложим оптимальное решение. Закажите разработку React-каталога и повысьте конверсию вашего магазина.







