1С Битрикс · 7 мин чтения

AJAX-фильтр каталога в Битрикс: обновление товаров без перезагрузки страницы

AJAX-фильтр каталога в Битрикс делаю почти на каждом проекте с интернет-магазином: клиент хочет, чтобы посетитель кликнул по чекбоксу «в наличии» или подвинул ползунок цены, и список товаров обновился за доли секунды, без белого экрана и прыжка страницы наверх. Стандартный bitrix:catalog.smart.filter умеет это из коробки, но на практике его почти всегда приходится дорабатывать под конкретный дизайн и нагрузку каталога на 5 000-50 000 товаров. Разберу, как это устроено изнутри, какие грабли встречаются чаще всего и что делать с индексацией, когда фильтр начинает плодить тысячи комбинаций URL.

Как работает AJAX-фильтрация каталога в Битрикс изнутри

В типовой связке два компонента: bitrix:catalog.smart.filter рисует форму фильтра и следит за изменением полей, а bitrix:catalog.section или bitrix:catalog выводит сетку товаров. При изменении любого поля фильтр не отправляет обычный submit формы, а генерирует GET-запрос с параметрами свойств и ценового диапазона и дергает второй компонент через встроенный в ядро Bitrix AJAX-механизм.

Включается это через параметры самого компонента вывода товаров: AJAX_MODE, AJAX_OPTION_JUMP, AJAX_OPTION_STYLE и AJAX_OPTION_HISTORY. Когда AJAX_MODE="Y", ядро само перехватывает клики по пагинации и события фильтра внутри области компонента и подменяет только HTML внутри обертки, не трогая остальную страницу. Это не самописный AJAX, а штатная возможность компонентного движка, которая на 70% проектов закрывает задачу без единой строчки своего JS.

Проблема в том, что «из коробки» это работает только с типовым шаблоном .default. Как только верстка каталога кастомная (карточки на Vue-компонентах, фильтр в сайдбаре с собственной анимацией), приходится либо переписывать шаблон компонента под родной AJAX-движок, либо выносить логику в собственный обработчик.

Настройка компонента catalog.section для динамического обновления каталога

Базовая настройка компонента вывода товаров под AJAX выглядит так:

$APPLICATION->IncludeComponent(
 "bitrix:catalog.section",
 "catalog_ajax",
 array(
 "IBLOCK_TYPE" => "catalog",
 "IBLOCK_ID" => 21,
 "SECTION_ID" => $arResult["VARIABLES"]["SECTION_ID"],
 "AJAX_MODE" => "Y",
 "AJAX_OPTION_JUMP" => "N",
 "AJAX_OPTION_STYLE" => "Y",
 "AJAX_OPTION_HISTORY" => "Y",
 "AJAX_OPTION_ADDITIONAL" => "",
 "PAGE_ELEMENT_COUNT" => 24,
 "CACHE_TYPE" => "A",
 "CACHE_TIME" => 3600,
 "CACHE_GROUPS" => "N",
 "SEF_MODE" => "Y",
 )
);

AJAX_OPTION_HISTORY="Y" критичен: без него после фильтрации кнопка «назад» в браузере уводит не на предыдущее состояние каталога, а вообще с сайта. CACHE_GROUPS="N" отключаю почти всегда, потому что группа пользователя редко влияет на список товаров, а на кеш с учетом групп натыкался с багами показа цен не той аудитории.

Рядом ставится bitrix:catalog.smart.filter с параметром SAVE_IN_SESSION, который запоминает последний выбранный фильтр в сессии, и FILTER_VIEW_MODE, отвечающим за то, будут ли значения свойств показываться списком чекбоксов или диапазоном.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Свой JS-обработчик: обновляем товары без перезагрузки страницы

Штатный AJAX_MODE устраивает не всегда: если карточки товара рендерятся не PHP-шаблоном, а фронтовым компонентом, проще перехватить событие фильтра и получить чистый JSON. Для этого использую компонентный экшен через BitrixMainEngineActionFilter либо старый добрый “AJAX_action” с проверкой bitrix_sessid().

document.querySelectorAll('.smart-filter input').forEach(function (input) {
 input.addEventListener('change', debounce(applyFilter, 300));
});

function applyFilter() {
 var form = document.getElementById('smart-filter-form');
 var params = new URLSearchParams(new FormData(form));

 fetch('/catalog/ajax_filter.php?' + params.toString(), {
 headers: { 'X-Requested-With': 'XMLHttpRequest' }
 })
 .then(function (r) { return r.json(); })
 .then(function (data) {
 document.getElementById('catalog-grid').innerHTML = data.html;
 document.getElementById('filter-count').textContent = data.count;
 window.history.pushState({}, '', data.url);
 initLazyLoad();
 });
}

function debounce(fn, delay) {
 var timer;
 return function () {
 clearTimeout(timer);
 timer = setTimeout(fn, delay);
 };
}

Дебаунс на 300 мс обязателен: без него каждое движение слайдера цены плодит по 10-15 запросов подряд, и сервер начинает отвечать медленнее, чем пользователь успевает щелкнуть следующий чекбокс. На стороне PHP ajax_filter.php подключает тот же catalog.section через буферизацию вывода (ob_start()/ob_get_clean()) и отдает готовый HTML вместе со счетчиком найденных товаров одним JSON-ответом.

Способ Скорость внедрения Гибкость верстки Особенности поддержки
AJAX_MODE=“Y” на компоненте 1-2 часа Ограничена стандартным шаблоном Работает «из коробки», меньше своего кода
Свой fetch + history.pushState 1-3 дня Полная свобода верстки и анимаций Требует ревизии после обновлений ядра модуля catalog

Типичные проблемы AJAX-фильтра каталога и как я их решаю

  • После замены DOM перестают работать слайдер изображений и лайтбокс. Инициализация была завязана на DOMContentLoaded, который срабатывает один раз. Решение - вынести инициализацию в отдельную функцию и вызывать ее заново после каждой AJAX-подмены блока.
  • Пагинация сбрасывается на первую страницу при любом изменении фильтра, хотя пользователь ждал, что список просто уменьшится. Обычно это осознанное поведение, но если нет, нужно передавать текущий номер страницы в запрос и валидировать его против нового количества найденных товаров.
  • Счетчики «сколько товаров осталось» рядом с чекбоксами показывают устаревшие цифры. Для точного счетчика приходится делать отдельный подзапрос по CIBlockResult::GetList с текущим набором фильтров минус один параметр, иначе цифры не будут учитывать уже выбранные значения.
  • Двойные запросы при быстром клике: пользователь щелкает два чекбокса подряд, и ответы приходят в непредсказуемом порядке, из-за чего на экране остается устаревший список. Лечится через AbortController, который отменяет предыдущий fetch перед стартом нового.

Отдельно всплывает ошибка 403 или «Ошибка проверки CSRF» на кешированном фрагменте: если компонент фильтра закеширован целиком вместе с формой, в кеш попадает и устаревший sessid. Для смарт-фильтра кеш обычно отключаю (CACHE_TYPE => "N") либо использую динамические плейсхолдеры, которые Bitrix сам подставляет поверх закешированного HTML.

Если фильтр на проекте оброс костылями за пару лет правок и разобраться в логике без пары дней раскопок уже нельзя, обычно такие задачи беру как отдельную доработку каталога и интернет-магазина, а не патч поверх патча.

SEO для AJAX-каталога: индексация и защита от дублей

AJAX сам по себе на позиции не влияет, роботы Яндекса и Google не кликают по чекбоксам. Проблема в другом: если у каждой комбинации фильтра формируется собственный URL с параметрами, каталог из 15 000 товаров легко порождает миллионы уникальных адресов, и поисковик начинает индексировать мусорные страницы вместо целевых категорий.

Что делаю на практике:

  • Оставляю в SEF-виде и открытыми для индексации только реально популярные комбинации - обычно 1 свойство или 2 связанных (например, «бренд + материал»), их беру из отчета Яндекс.Метрики по посещаемым URL за 3-5 месяцев.
  • На страницы с 3+ выбранными фильтрами ставлю <meta name="robots" content="noindex, follow"> и canonical на базовую страницу раздела без параметров.
  • В robots.txt закрываю параметры пагинации и сортировки вида ?sort= и ?PAGEN_1=, чтобы не плодить дубли по порядку сортировки.
  • Название страницы (title, h1) для индексируемых комбинаций генерирую динамически по выбранным свойствам, а не оставляю статичный заголовок раздела - иначе все отфильтрованные страницы конкурируют друг с другом за один и тот же запрос.

Кеширование и скорость ответа фильтра

Без кеша на каталоге в 20 000+ товаров ответ фильтра с несколькими свойствами и подсчетом остатков легко улетает за 500-700 мс, и пользователь это чувствует как подвисание. Обычную кешировку компонента (CACHE_TYPE => "A") для фильтра напрямую не применить, потому что результат зависит от переданных GET-параметров, а не только от раздела.

Что помогает держать ответ в пределах 100-150 мс:

  • Управляемый кеш с тегами (BitrixMainDataTaggedCache) на уровне выборки товаров, с инвалидацией по конкретному инфоблоку при изменении остатков и цен, а не по времени.
  • Отдельный кеш для «счетчиков» рядом с чекбоксами, обновляемый раз в 5-10 минут - для интернет-магазина точность до товара тут не критична, а нагрузка на БД падает в разы.
  • Композитный кеш Bitrix для первого захода на страницу раздела и отдельная, более легкая логика для AJAX-запросов фильтра, где рендерится только карточка товара без шапки, футера и меню.
  • Индексы в БД под часто фильтруемые свойства (SKU-свойства и цены) - без них выборка по 40 000 товаров с 5 условиями фильтра уходит в полный скан таблицы значений свойств.

На одном из проектов с каталогом стройматериалов (около 12 000 позиций, 18 фильтруемых свойств) переход с обычного кеша компонента на тегированный кеш плюс индексы сократил среднее время ответа AJAX-фильтра с 640 мс до 110 мс - разница ощущается уже на глаз, без всяких замеров.

Чтобы сайт работал без сбоев

Техподдержка

от 15 000 ₽/мес

Подробнее →

Частые вопросы

Можно ли включить AJAX-фильтр без переписывания шаблона компонента?

Да, если верстка каталога близка к стандартной. Достаточно добавить параметры AJAX_MODE, AJAX_OPTION_STYLE и AJAX_OPTION_HISTORY в компонент вывода товаров и убедиться, что смарт-фильтр отправляет запросы в ту же обертку. Для нестандартной верстки на компонентах вроде Vue или React проще написать свой JS-обработчик поверх компонентного экшена.

Почему после AJAX-обновления каталога ломаются слайдеры и лайтбоксы на карточках товара?

Потому что скрипты инициализации привязаны к событию загрузки страницы, которое срабатывает один раз. После каждой подмены блока товаров нужно заново вызывать функции инициализации плагинов - слайдера, лайтбокса, ленивой загрузки картинок - именно для той части DOM, которая была заменена.

Влияет ли AJAX-фильтр на позиции сайта в Яндексе и Google?

Сам факт AJAX-обновления не влияет: роботы не кликают по чекбоксам и оценивают страницу по исходному HTML. Проблема возникает, если каждая комбинация фильтра формирует индексируемый URL - тогда каталог начинает конкурировать сам с собой за одни и те же запросы. Решается через canonical, meta robots на «мусорных» комбинациях и точечное открытие для индексации только популярных фильтров.

Сколько стоит внедрить AJAX-фильтр в существующий каталог на Битрикс?

Зависит от состояния текущего кода: если каталог собран на типовых компонентах и нужно включить штатный AJAX_MODE с доработкой шаблона под дизайн, это укладывается в рамки доработки интернет-магазина под ключ, от 80 000 ₽. Если требуется только точечная правка уже работающего фильтра или разбор багов после чужих правок, обычно хватает разовой консультации от 3 000 ₽, чтобы оценить объем и не переплачивать за то, что можно починить за пару часов.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно