Три года назад мне позвонил владелец интернет-магазина автозапчастей и сказал фразу, которую я с тех пор слышу регулярно: «У нас каталог на пятнадцать тысяч позиций, и люди уходят, не дойдя до нужного товара». Я зашёл на его сайт, выбрал в фильтре марку автомобиля — страница перезагрузилась. Выбрал тип запчасти — снова перезагрузка. Попробовал указать ценовой диапазон — ещё одна перезагрузка, причём все предыдущие фильтры сбросились. Три клика — пятнадцать секунд ожидания и полная потеря контекста. Я закрыл вкладку. И я — человек, который профессионально занимается интернет-магазинами. А что делает обычный покупатель? Правильно, идёт на Wildberries, где фильтрация работает мгновенно.
Это не история одного магазина. Это системная проблема WooCommerce, которую я наблюдаю практически на каждом проекте, где ещё не поставили нормальный AJAX-фильтр. Стандартный механизм фильтрации в WooCommerce устроен предельно просто: вы выбираете параметр, браузер отправляет GET-запрос с новыми параметрами, сервер обрабатывает запрос целиком — от парсинга URL до рендеринга полной HTML-страницы, — и возвращает результат. На каждый клик по фильтру — полный цикл: запрос к базе данных, сборка шаблона, отправка всего HTML обратно в браузер. И если у вас десять тысяч товаров, каждый с десятком атрибутов, и к тому же сервер не из топовых — каждая такая перезагрузка может занимать две-три секунды. Умножьте это на пять-шесть кликов, которые нужны, чтобы сузить выборку до нужных товаров, и вы получите пятнадцать-двадцать секунд чистого ожидания. За это время покупатель успевает передумать, отвлечься или просто уйти.
Я долго изучал эту проблему — не как разработчик, а как маркетолог. Потому что для маркетолога фильтрация каталога — это не техническая деталь, а критический элемент воронки продаж. Между «зашёл в каталог» и «добавил в корзину» стоит именно фильтр, и если он работает плохо — воронка течёт именно здесь. По данным Baymard Institute, сорок два процента крупных интернет-магазинов теряют покупателей из-за неудобной фильтрации. Не из-за цен, не из-за ассортимента — из-за того, что человек не может быстро найти то, что ему нужно. И когда я начинал проектировать модуль фильтрации для нашего плагина, я ставил себе одну задачу: сделать так, чтобы покупатель даже не замечал, что фильтр работает. Никаких перезагрузок, никаких задержек, никакого ощущения «я жду, пока компьютер подумает».
Вот что из этого получилось — и почему под капотом оказалось значительно сложнее, чем кажется на первый взгляд.
Почему стандартная фильтрация WooCommerce — это боль
Чтобы понять, почему AJAX-фильтр — это не просто «красивая фишка», а необходимость, давайте разберёмся, что происходит, когда покупатель фильтрует товары в стандартном WooCommerce. Допустим, у вас магазин промышленных масел. Покупатель заходит в каталог, видит восемьсот товаров и хочет выбрать моторное масло вязкостью 5W-30 от определённого бренда. Он кликает на виджет «Фильтр по атрибуту», выбирает «Моторные масла» — и страница полностью перезагружается. Браузер отправляет GET-запрос вроде «?filter_tip-masla=motornoe», сервер обрабатывает его, WooCommerce запускает WP_Query с мета-запросами, MySQL перебирает wp_postmeta — таблицу, которая на проекте с шестнадцатью тысячами товаров может весить под гигабайт, — и через пару секунд возвращает результат. Покупатель видит, скажем, триста товаров. Теперь он хочет выбрать вязкость. Ещё один клик — ещё одна перезагрузка, ещё две секунды. Потом бренд — третья перезагрузка. И вот что самое обидное: на некоторых темах и конфигурациях при очередной перезагрузке предыдущие фильтры сбрасываются. Покупатель начинает заново.
Я видел это десятки раз на реальных проектах. Владелец магазина вкладывает деньги в рекламу, платит за каждый клик в Яндекс.Директ, приводит человека на сайт — и теряет его на этапе фильтрации. Причём не потому, что товара нет, а потому, что товар есть, но его невозможно нормально найти. Это как магазин, в котором весь товар свален в одну кучу, а продавец на каждый вопрос отвечает с задержкой в три секунды. Вы бы ушли? Я бы ушёл.
Есть ещё одна проблема, о которой мало говорят: стандартная фильтрация WooCommerce очень плохо работает с SEO. Каждый вариант фильтра генерирует уникальный URL с GET-параметрами, и если поисковый бот начинает индексировать все эти комбинации — а комбинаций при десяти атрибутах с десятью значениями каждый может быть десять в десятой степени — вы получаете раздутый индекс, дублированный контент и размытый краулинговый бюджет. Для маленького магазина с сотней товаров это не критично, но для серьёзного каталога — реальная проблема.
Собственно, именно поэтому все крупные маркетплейсы — Ozon, Wildberries, Яндекс.Маркет — давно перешли на AJAX-фильтрацию. Когда покупатель выбирает параметр, браузер отправляет асинхронный запрос на сервер, получает только данные — без шапки, подвала и боковой панели — и обновляет только ту часть страницы, где показаны товары. Ни мерцания экрана, ни перезагрузки, ни потери скролла. Вы кликаете — товары моментально меняются. Это стандарт индустрии, и если ваш магазин его не обеспечивает — вы проигрываете уже на уровне пользовательского опыта.
Но — и вот тут начинается самое интересное — просто перевести фильтрацию на AJAX недостаточно. Если под капотом остаётся тот же WP_Query с джойнами по wp_postmeta, вы просто уберёте перезагрузку, но скорость ответа останется прежней — те же полторы-две секунды на каждый запрос. Покупатель не увидит перезагрузки страницы, но увидит спиннер на месте списка товаров. А разница между «страница перезагружается» и «товары не появляются» с точки зрения терпения покупателя — примерно нулевая. Поэтому при проектировании нашего модуля фильтрации я сразу заложил радикально другой подход к хранению и выборке данных.
FilterIndex: когда пятьдесят миллисекунд — это не маркетинг, а реальность
Основная идея, которая отличает наш AJAX-фильтр от большинства решений на рынке, — это предвычисленный индекс. Называется он FilterIndex, и работает по принципу, который используют поисковые системы: вместо того чтобы каждый раз при запросе пользователя перебирать все товары и их атрибуты, мы заранее строим оптимизированную таблицу, в которой уже есть все нужные связки «товар — атрибут — значение — цена — наличие».
Как это работает технически? Когда администратор настраивает фильтры — допустим, добавляет фильтрацию по бренду, вязкости и типу масла — система запускает фоновую задачу через Action Scheduler. Эта задача проходит по всем товарам каталога и для каждого товара записывает в отдельную таблицу wpaic_filter_index набор строк: товар такой-то, атрибут «Бренд», значение «Shell»; товар такой-то, атрибут «Вязкость», значение «5W-30»; и так далее. Плюс к каждой строке добавляется цена товара, статус наличия, категория и другая информация, которая может понадобиться для фильтрации. По сути, мы берём данные, разбросанные по десяткам таблиц WordPress — wp_posts, wp_postmeta, wp_terms, wp_term_relationships, wp_wc_product_meta_lookup — и складываем их в одну плоскую таблицу с правильными индексами.
Результат? Запрос, который в стандартной WooCommerce-фильтрации требовал четыре-пять JOIN-операций по таблице wp_postmeta в гигабайт размером и выполнялся за полторы-две секунды, теперь выполняется за тридцать-пятьдесят миллисекунд. Это не теоретическая цифра — это реальные замеры на каталоге из шестнадцати тысяч товаров с девяносто девятью атрибутами. Разница в скорости — в тридцать-сорок раз. И покупатель эту разницу чувствует буквально: он кликает по фильтру — и товары появляются мгновенно, без какого-либо ожидания.
Но у предвычисленного индекса есть очевидный вопрос: а что, если данные изменились? Добавили новый товар, поменяли цену, обновили остатки из 1С? Вот тут работает система хуков. При изменении любого товара — через стандартное редактирование в WordPress, через REST API, через импорт из 1С — срабатывает хук, который обновляет соответствующие строки в индексе. Причём обновляет не весь индекс целиком, а только строки конкретного товара. Полная перестройка индекса запускается только по команде администратора или по расписанию — например, раз в сутки для гарантии консистентности. В обычном режиме индекс поддерживается в актуальном состоянии инкрементально, практически в реальном времени.
Я помню, как один клиент, владелец магазина с двадцатью тысячами товаров, спросил меня: «А вы уверены, что индекс не рассинхронизируется?» Это справедливый вопрос — я сам задавал его себе на этапе проектирования. И ответ такой: да, при определённых обстоятельствах — например, при прямом редактировании базы данных мимо WordPress — индекс может отстать. Именно поэтому в панели управления есть кнопка ручной перестройки индекса и настройка автоматической пересборки по cron. Но в штатном режиме, когда товары меняются через WooCommerce или через наш 1С-коннектор, рассинхронизация исключена.
Ещё один момент, который я считаю важным: индекс хранит не только значения атрибутов, но и количество товаров для каждого значения. Это то, что называется «фасетные счётчики» — когда рядом с каждым значением фильтра показывается число товаров, которые ему соответствуют. В стандартной WooCommerce-фильтрации эти счётчики либо не показываются, либо требуют дополнительного запроса к базе. В нашем случае счётчики вычисляются прямо в момент запроса, практически без дополнительных затрат, потому что индекс уже содержит всю необходимую информацию. И когда покупатель выбирает «Shell» в фильтре по бренду, он сразу видит, что моторных масел Shell вязкостью 5W-30 в наличии — двенадцать штук, а вязкостью 10W-40 — восемь. Это убирает ситуацию «выбрал параметр — получил ноль результатов», которая дико раздражает покупателей.
Кстати, о нулевых результатах. Одна из проблем стандартной фильтрации — так называемые «мёртвые комбинации». Покупатель выбирает бренд, потом тип масла, потом вязкость — и получает пустую страницу, потому что товаров с такой комбинацией параметров нет. В нашем фильтре мёртвые комбинации блокируются автоматически: после каждого выбора параметра пересчитываются счётчики для всех остальных значений, и значения с нулём товаров либо скрываются, либо показываются неактивными. Покупатель физически не может прийти к пустой выборке — система его направляет, показывая только релевантные варианты.
Визуальные фильтры и свотчи: когда интерфейс продаёт
Честно говоря, скорость фильтрации — это фундамент, но не то, что покупатель замечает первым. Первое, что бросается в глаза, — это внешний вид фильтров. И тут у стандартного WooCommerce всё совсем грустно: выпадающие списки с текстовыми значениями. «Красный», «Синий», «Зелёный» — написано текстом. А теперь представьте, что вы продаёте краску, ткань, мебель или одежду. Покупателю нужно видеть цвет, а не читать его название. Он хочет кликнуть на красный кружочек и увидеть все красные товары. Или кликнуть на логотип Bosch и увидеть всё от Bosch, не вспоминая, как правильно пишется название — Bosch, BOSCH или Бош.
Мы реализовали три типа визуальных фильтров. Первый — цветовые свотчи. Каждому значению атрибута «Цвет» можно назначить HEX-код цвета, и в фильтре вместо текстового списка покупатель видит ряд цветных кружочков. Кликнул на красный — получил красные товары. Причём кружочки не просто статичные — выбранный цвет подсвечивается рамкой, а при наведении показывается всплывающая подсказка с названием. Второй тип — кнопки. Это хорошо работает для размеров (S, M, L, XL, XXL) или для дискретных значений (500 мл, 1 л, 5 л, 20 л). Вместо выпадающего списка — ряд аккуратных кнопок, которые можно кликать и комбинировать. Третий тип — логотипы. Для атрибута «Бренд» каждому значению можно загрузить изображение логотипа, и покупатель выбирает бренд по визуальному образу, а не по тексту. Это особенно важно для магазинов с международными брендами, где у покупателя может быть лучше визуальная память на логотип, чем на написание названия.
И вот здесь я хочу отдельно остановиться на свотчах не в фильтре, а на карточке товара. Потому что это связанная, но отдельная задача. Когда покупатель нашёл товар через фильтр и перешёл на карточку — ему нужно выбрать конкретный вариант. Цвет, размер, объём — в WooCommerce это реализовано через вариативные товары и стандартные выпадающие списки. И эти списки — одна из самых больших упущенных возможностей WooCommerce с точки зрения конверсии.
Наш модуль свотчей заменяет выпадающие списки на визуальные элементы прямо на карточке товара. Для цвета — кружочки с реальным цветом. Для размера — кнопки. Для бренда — миниатюры логотипов. Но самое главное — при выборе варианта автоматически меняется изображение товара. Покупатель кликает на синий кружочек — и на главном фото товара появляется именно синий вариант. Кликает на красный — видит красный. Это мелочь с технической точки зрения, но с маркетинговой — огромный рычаг. Покупатель, который видит товар в нужном ему цвете, значительно ближе к покупке, чем тот, кто видит только текст «Синий» и должен сам представить, как это выглядит.
Я проводил неформальное исследование на трёх проектах, где мы внедряли свотчи. До внедрения конверсия из карточки товара в корзину составляла в среднем восемь процентов. После внедрения визуальных свотчей с автоматической сменой изображения — одиннадцать-двенадцать процентов. Рост на тридцать-пятьдесят процентов. Понятно, что это не чистый A/B-тест, и были другие факторы, но тенденция однозначная. Люди покупают то, что видят, а не то, что читают.
Ещё один нюанс, который мне кажется важным: свотчи и визуальные фильтры должны быть единой системой. Если в фильтре каталога «красный» — это кружочек с цветом #FF0000, а на карточке товара «красный» — это текст в выпадающем списке, покупатель чувствует дисконнект. В нашем плагине настройки свотчей глобальные: вы один раз назначаете атрибуту «Цвет» тип отображения «Color swatch» и задаёте HEX-коды для каждого значения — и дальше эти свотчи используются везде: в фильтре, на карточке товара, в виджетах, в результатах поиска. Одна настройка — единый визуальный язык по всему магазину.
Но давайте поговорим о том, что происходит ниже списка товаров — о пагинации. Потому что даже идеальный фильтр не поможет, если после фильтрации остаётся двести товаров и покупателю нужно переключать страницы, чтобы увидеть их все.
«Показать ещё» вместо пагинации: маленькая кнопка, большой эффект
Знаете что меня всегда удивляло в WooCommerce? Стандартная пагинация — «1, 2, 3, ... 15, Следующая» — была придумана в эпоху, когда каждая страница загружалась полностью и пользователь сидел за десктопом с мышкой. Сегодня больше шестидесяти процентов покупателей приходят с мобильных, и для них переключение страниц — это мучение. Мелкие ссылки, на которые нужно попадать пальцем, плюс каждый переход — перезагрузка страницы, плюс потеря позиции скролла. Ozon и Wildberries давно заменили это на кнопку «Показать ещё» и бесконечную подгрузку, и не случайно — это просто удобнее.
В нашем плагине мы реализовали модуль Load More, который заменяет стандартную пагинацию WooCommerce на кнопку «Показать ещё». Покупатель долистал до конца текущей порции товаров, нажал одну большую кнопку — и следующая порция плавно добавляется к уже загруженным. Без перезагрузки, без потери скролла, без необходимости целиться в мелкие цифры пагинации. И это прекрасно работает в связке с AJAX-фильтром: покупатель выбрал параметры, увидел первые двадцать товаров, нажал «Показать ещё» — увидел ещё двадцать. Всё на одной странице, всё без мерцания.
Технически это работает так: при нажатии на кнопку JavaScript отправляет AJAX-запрос с текущими параметрами фильтрации и номером следующей «страницы». Сервер возвращает HTML-разметку следующей порции товаров, и скрипт вставляет её в DOM после последнего товара. URL в адресной строке при этом не меняется, скролл сохраняется, выбранные фильтры остаются на месте. Для покупателя это выглядит так, будто товары просто «появляются» внизу списка.
Но есть тонкость, о которой я хочу рассказать, потому что на ней спотыкаются многие разработчики. Когда вы используете бесконечную подгрузку на WooCommerce, нужно правильно обрабатывать ситуацию, когда товары заканчиваются. Если покупатель добрался до последней порции — кнопка «Показать ещё» должна исчезнуть, и желательно показать ненавязчивое сообщение «Все товары загружены». Звучит банально, но я видел реализации, где кнопка продолжала отображаться и при нажатии возвращала пустой ответ, оставляя покупателя в недоумении. Или, наоборот, кнопка исчезала, но при изменении фильтров не появлялась обратно, и покупатель видел только первые двадцать товаров из двухсот. В нашем модуле кнопка управляется состоянием: она знает, сколько товаров осталось, и показывает это покупателю — «Показать ещё (осталось 45)». При изменении фильтров счётчик сбрасывается и пересчитывается автоматически.
Есть ещё один момент, который я считаю критически важным для мобильных пользователей: размер и расположение кнопки. Стандартные пагинационные ссылки WooCommerce — это мелкие цифры, в которые сложно попасть пальцем на телефоне. Наша кнопка «Показать ещё» — это полноширинная кнопка с крупным текстом, которую невозможно промахнуть. Она стилизована под общий дизайн магазина через CSS-переменные, и администратор может настроить её цвет, скругление, отступы — но по умолчанию она уже выглядит хорошо и, главное, удобно нажимается с любого устройства.
И тут возникает вопрос, который мне задают регулярно: а что с SEO? Если все товары подгружаются через AJAX, как поисковые боты увидят полный каталог? Это действительно важный вопрос, и ответ на него — в архитектуре. Наш модуль Load More не заменяет серверную пагинацию, а дополняет её. В HTML-коде страницы остаются стандартные ссылки на следующие страницы — rel="next" и rel="prev". Поисковый бот, который не выполняет JavaScript, проходит по этим ссылкам и индексирует все страницы каталога как обычно. А покупатель, у которого JavaScript работает, видит кнопку «Показать ещё» вместо пагинационных ссылок и подгружает товары через AJAX. Таким образом, мы получаем лучшее из двух миров: удобный UX для покупателя и полную индексацию для поискового бота.
Кстати, про связку фильтра и подгрузки. Когда покупатель меняет параметры фильтра, модуль Load More автоматически сбрасывается: показывается первая порция товаров по новым критериям, счётчик обнуляется, кнопка обновляет количество оставшихся товаров. Это кажется очевидным, но на практике интеграция двух AJAX-модулей — фильтрации и подгрузки — требует аккуратной координации. Они должны использовать общее состояние, и мы потратили немало времени, чтобы сделать эту связку бесшовной.
Теперь давайте поговорим о том, как всё это работает на мобильных устройствах. Потому что мобильная фильтрация — это отдельная история, со своими проблемами и решениями.
На десктопе фильтры обычно располагаются в боковой панели слева от списка товаров. Экран большой, места хватает, панель фильтров видна постоянно — кликай и выбирай. На мобильном такой роскоши нет. Боковая панель просто не помещается рядом с товарами на экране шириной в триста девяносто пикселей. И тут есть два типичных решения, оба неудачных. Первое — показать фильтры над товарами, длинным списком. Покупатель видит экран, забитый фильтрами, и должен проскроллить их, чтобы увидеть хоть один товар. Второе — спрятать фильтры в стандартный элемент «аккордеон» или выпадающий блок. Покупатель кликает — фильтры раскрываются, но при этом сдвигают товары вниз, и на экране всё прыгает.
Мы пошли третьим путём: сворачиваемая панель фильтров в виде оверлея. На мобильном устройстве покупатель видит кнопку «Фильтры» — обычно в верхней части каталога, рядом с сортировкой и количеством товаров. При нажатии на эту кнопку панель фильтров выезжает сбоку — как меню на многих мобильных сайтах — и занимает примерно восемьдесят процентов экрана. Покупатель выбирает нужные параметры, нажимает «Показать» — панель закрывается, а товары обновляются. Важно, что при этом товары обновляются в реальном времени, прямо пока панель открыта: покупатель видит счётчик «Найдено: 42 товара» на кнопке «Показать», и этот счётчик обновляется при каждом изменении фильтра. Это даёт покупателю мгновенную обратную связь: он понимает, сужает ли он выборку слишком сильно или нет.
Ещё один приём, который мы используем на мобильных, — это «чипсы» выбранных фильтров. После закрытия панели фильтров, над списком товаров появляются компактные «чипсы» — маленькие плашки с выбранными значениями: «Shell», «5W-30», «В наличии». Каждый чипс можно удалить одним тапом, сняв соответствующий фильтр. Это гораздо удобнее, чем снова открывать панель, чтобы снять один параметр. И это экономит покупателю время — а время на мобильном ценится вдвойне, потому что мобильные сессии обычно короче десктопных.
Я заметил интересную вещь: на мобильных устройствах покупатели в среднем используют меньше фильтров одновременно — обычно один-два, максимум три. Но используют они их чаще и быстрее. Поэтому критически важна скорость отклика: пятьдесят миллисекунд ответа индекса здесь играют ещё большую роль, чем на десктопе. Если мобильный покупатель нажал на фильтр и ждёт больше секунды — он, скорее всего, решит, что что-то сломалось, и начнёт нажимать повторно. А двойной клик по фильтру обычно означает выбор и немедленную отмену — и покупатель остаётся с тем, с чего начал, только раздражённый.
Давайте теперь поговорим о ситуации, с которой сталкиваются многие владельцы магазинов: миграция с существующего решения. Потому что большинство магазинов на WooCommerce не строятся с нуля — у них уже стоит какой-то фильтр, обычно YITH Ajax Product Filter или WooCommerce Product Filter от другого разработчика.
Миграция с YITH и других фильтров: как не потерять данные и нервы
Я неоднократно сталкивался с ситуацией, когда магазин годами использует YITH Ajax Product Filter, и владелец хочет перейти на что-то другое. Причины бывают разные: YITH перестал обновляться, стал конфликтовать с темой, слишком медленно работает на большом каталоге, или просто надоело платить отдельно за фильтр, отдельно за свотчи, отдельно за пагинацию. И первый вопрос, который задаёт владелец: «А настройки фильтров сохранятся?»
Давайте будем честными: стопроцентной автоматической миграции настроек между разными плагинами фильтрации не бывает. Слишком разные архитектуры, слишком разные способы хранения конфигурации. YITH хранит свои пресеты в опциях WordPress и в постмета, другие плагины используют кастомные таблицы, третьи — JSON-файлы. Но мигрировать вручную тоже не нужно — в нашем модуле есть инструмент миграции, который анализирует установленные плагины фильтрации и предлагает маппинг настроек.
Как это работает на практике? При первой активации нашего модуля фильтрации система проверяет, установлен ли YITH Ajax Product Filter (или YITH WooCommerce Ajax Product Filter Premium). Если да — в интерфейсе настроек появляется вкладка «Миграция» с информацией о найденных пресетах YITH, настроенных фильтрах и их типах. Администратор видит, какие атрибуты были настроены в YITH, какие типы отображения использовались (чекбокс, выпадающий список, цветовой свотч, ценовой слайдер), и может запустить миграцию одной кнопкой. Система создаёт аналогичные фильтры в нашем модуле, маппит типы отображения (YITH «color» → наш «swatch», YITH «label» → наш «button» и так далее) и строит индекс.
Но я хочу предупредить: после миграции обязательно нужно проверить результат вручную. Не потому, что миграция ненадёжна, а потому, что это отличный повод пересмотреть структуру фильтров. Часто бывает, что фильтры в YITH настраивались два-три года назад, с тех пор ассортимент поменялся, появились новые атрибуты, какие-то значения устарели — и миграция слепо копирует всё как есть, включая уже неактуальные настройки. Я рекомендую клиентам использовать миграцию как отправную точку, а потом пройтись по каждому фильтру и спросить себя: «А покупателю это действительно нужно?»
Кстати, один из самых частых вопросов при миграции — «а что будет с URL?». У YITH свой формат фильтрационных URL (обычно с хешами или кастомными параметрами), у нашего модуля — свой. Если вы активно использовали фильтрационные URL в рекламе или внутренних ссылках — при миграции эти URL перестанут работать. В таких случаях я рекомендую настроить редиректы через наш же модуль SEO-редиректов, который поддерживает regex-паттерны. Пара правил — и старые фильтрационные ссылки будут корректно перенаправлять на новые.
Ещё один практический совет из опыта миграций: не отключайте старый фильтр до того, как полностью настроите новый. Звучит очевидно, но я видел, как люди деактивируют YITH, активируют наш модуль и начинают настраивать — а в это время живой магазин работает без фильтрации вообще. На это может уйти час-два, и за это время вы потеряете какое-то количество продаж. Лучше настроить новый фильтр параллельно (он не будет конфликтовать, пока его шорткод не размещён на странице), убедиться, что всё работает, и переключиться одномоментно.
Я хочу ещё раз подчеркнуть мысль, к которой я возвращаюсь постоянно: фильтрация каталога — это не техническая фича, а элемент продаж. Каждый магазин, который переходил с стандартной фильтрации WooCommerce или с устаревшего плагина на наш AJAX-фильтр с предвычисленным индексом, замечал улучшение поведенческих метрик. Снижение показателя отказов на странице каталога, увеличение глубины просмотра, рост времени на сайте. И, что особенно важно, — рост конверсии из просмотра каталога в добавление в корзину. Потому что когда покупатель быстро находит то, что ему нужно, он с большей вероятностью это купит.
Давайте теперь поговорим о том, как всё это собирается в единую систему и почему подход «один плагин для всего» здесь имеет принципиальное значение.
Знаете, что я считаю главной проблемой подхода «фильтр от одного разработчика, свотчи от другого, пагинация от третьего»? Это не конфликты плагинов, хотя они тоже бывают. Это разрыв пользовательского опыта. Покупатель выбирает цвет в фильтре — видит красивый кружочек. Переходит на карточку товара — а там текстовый выпадающий список. Нажимает «Показать ещё» — а фильтры сбрасываются, потому что плагин пагинации не знает про плагин фильтрации. Каждый из этих плагинов отлично работает сам по себе, но вместе они создают ощущение Франкенштейна — сшитого из разных кусков, неровного, с торчащими швами.
Когда фильтр, свотчи, подгрузка товаров и индексация — части одного модуля, они разделяют общее состояние. JavaScript-код фильтрации знает про модуль подгрузки и корректно с ним взаимодействует. Настройки свотчей применяются одинаково в фильтре и на карточке товара. Индекс фильтрации учитывает данные, которые обновляются через другие модули плагина — 1С-синхронизацию, массовое редактирование, импорт. Это не просто удобство разработчика — это принципиально другой уровень пользовательского опыта для покупателя.
Я часто привожу аналогию с автомобилем. Можно собрать машину из деталей разных производителей: двигатель от одного, подвеска от другого, электроника от третьего. Технически это возможно, и каждая деталь может быть превосходного качества. Но машина, спроектированная как единое целое, будет ехать лучше — потому что инженеры оптимизировали взаимодействие всех компонентов, а не каждый компонент по отдельности. То же самое с интернет-магазином: покупателю не нужен лучший в мире фильтр, отдельно лучшие свотчи и отдельно лучшая пагинация. Ему нужен каталог, который работает как единое целое — быстро, красиво и предсказуемо.
И тут я хочу вернуться к теме, с которой начал — к скорости. Потому что предвычисленный индекс — это не единственная оптимизация, которую мы сделали. Есть ещё несколько вещей, которые влияют на воспринимаемую скорость фильтрации. Во-первых, оптимистичное обновление: когда покупатель кликает на фильтр, товарная сетка мгновенно получает CSS-эффект «загрузки» — лёгкое затемнение или размытие — и одновременно отправляется AJAX-запрос. Когда ответ приходит (через тридцать-пятьдесят миллисекунд — напоминаю), товары обновляются, и эффект загрузки снимается. Для покупателя это выглядит как мгновенная реакция: он кликнул — сетка «мигнула» — новые товары на месте. Причём «мигнула» настолько быстро, что покупатель воспринимает это не как ожидание, а как визуальное подтверждение действия.
Во-вторых, кеширование на стороне клиента. Если покупатель выбрал «Shell, 5W-30» и получил двенадцать товаров, потом снял «5W-30» и посмотрел все масла Shell, а потом снова поставил «5W-30» — второй раз этот же запрос не пойдёт на сервер, а будет отдан из кеша браузера. Это экономит трафик и ускоряет навигацию при «возвратных» фильтрациях, когда покупатель пробует разные комбинации туда-сюда.
В-третьих — и это, пожалуй, самая неочевидная оптимизация — мы отдаём с сервера не полный HTML товарных карточек, а минимально необходимый. Стандартный WooCommerce-шаблон карточки товара в каталоге может содержать десятки хуков, каждый из которых добавляет HTML: кнопка сравнения от одного плагина, значок избранного от другого, рейтинг от третьего. На странице с двадцатью товарами это может быть двести-триста килобайт HTML. Мы оптимизировали серверный рендеринг так, чтобы AJAX-ответ содержал только необходимый минимум, а дополнительные элементы (кнопки сравнения, свотчи, значки) инициализировались на клиенте после вставки карточек в DOM. Результат — ответ сервера в три-четыре раза легче, и при медленном мобильном интернете это заметная разница.
Наконец, последнее, о чём я хочу сказать — это аналитика фильтрации. Мы встроили в модуль фильтрации сбор статистики: какие фильтры используются чаще всего, какие комбинации выбираются, какие значения никогда не кликаются, сколько раз покупатели приходят к нулевому результату. Эти данные доступны в панели управления и помогают владельцу магазина оптимизировать каталог. Если вы видите, что фильтр «Страна производства» используется в ноль целых два десятых процента фильтраций — может быть, его стоит убрать, чтобы не захламлять панель. Если видите, что покупатели часто фильтруют по бренду и сразу по вязкости — имеет смысл поставить эти два фильтра первыми. Если видите, что на мобильных покупатели используют только ценовой слайдер и бренд — можно для мобильной версии скрыть остальные фильтры, показывая их только по отдельному тапу «Все фильтры».
Вот в чём штука: фильтрация каталога — это не «поставил и забыл». Это живой инструмент, который нужно настраивать под свой ассортимент и своих покупателей. И чем больше данных вы собираете о том, как покупатели фильтруют — тем точнее вы можете настроить. Мы стараемся дать владельцу магазина не просто фильтр, а инструмент понимания того, как покупатели ищут товары.
Когда я начинал проектировать этот модуль, я ставил себе простую цель: чтобы покупатель в интернет-магазине на WooCommerce мог найти товар так же быстро и удобно, как на Ozon или Wildberries. Не «почти так же» и не «с некоторыми ограничениями», а действительно так же. Мгновенная фильтрация без перезагрузки, визуальные свотчи вместо текстовых списков, бесконечная подгрузка вместо нумерованных страниц, удобная мобильная панель фильтров. И чтобы всё это работало быстро — не за секунды, а за десятки миллисекунд.
Получилось ли? Я думаю, да — но не потому, что мы написали гениальный код, а потому, что правильно определили приоритеты. Мы начали не с красивого интерфейса, а с производительности — с предвычисленного индекса. Потому что красивый фильтр, который тормозит, — это всё равно плохой фильтр. А потом надстроили над быстрым ядром удобный интерфейс: свотчи, чипсы, мобильный оверлей, кнопку подгрузки. И связали это в единую систему, где каждый элемент знает про все остальные.
Если вы сейчас смотрите на свой WooCommerce-магазин и видите стандартные выпадающие списки в фильтрах, нумерованную пагинацию внизу каталога и перезагрузку страницы при каждом клике — подумайте о том, сколько покупателей вы теряете каждый день. Не из-за цен, не из-за ассортимента, а из-за того, что им просто неудобно искать. Модуль AJAX-фильтрации в COS WP Woo решает эту проблему комплексно: фильтрация, визуальное отображение, подгрузка, мобильная адаптация, индексация и аналитика — всё в одном плагине, без необходимости покупать и настраивать пять разных решений. Попробуйте — и посмотрите на свои метрики через неделю. Я уверен, что числа вас удивят.
А теперь — честный разговор о подводных камнях. Потому что я не люблю статьи, в которых всё идеально и ни одного минуса. Любая технология имеет свои ограничения, и AJAX-фильтрация — не исключение.
Первый подводный камень — это кеширование на уровне сервера. Если у вас стоит LiteSpeed, Varnish, nginx FastCGI cache или любой другой серверный кеш — AJAX-запросы фильтрации должны его обходить. Звучит просто, но на практике это требует правильной настройки. AJAX-запросы идут на тот же wp-admin/admin-ajax.php или на кастомный REST-эндпоинт, и серверный кеш может начать кешировать результаты фильтрации. В итоге покупатель выбирает «Shell» — а видит результаты для «Лукойл», потому что кеш отдаёт ответ от предыдущего запроса другого покупателя. Мы решаем это двумя способами: во-первых, используем POST-запросы для фильтрации (серверные кеши обычно не кешируют POST), во-вторых, добавляем заголовки no-cache к ответам. Но если вы настраиваете серверное кеширование самостоятельно — имейте это в виду, потому что кешированный AJAX-фильтр — это не просто баг, это баг, который показывает покупателю чужие результаты.
Второй камень — это доступность. Когда весь контент обновляется через JavaScript без перезагрузки страницы, скринридеры и другие вспомогательные технологии могут не заметить изменения. Мы добавляем ARIA-атрибуты к области товаров (aria-live="polite") и объявляем изменения для вспомогательных технологий при каждом обновлении. Это не заметно для обычного покупателя, но критически важно для людей, использующих скринридеры. И, кстати, это влияет на оценку доступности в Lighthouse, которая всё чаще становится фактором ранжирования.
Третий момент — это тяжёлые каталоги с кастомными полями. Если ваши товары используют не стандартные атрибуты WooCommerce, а кастомные поля (ACF, наш CF-модуль или просто wp_postmeta), индексация этих полей требует дополнительной настройки. Стандартные атрибуты WooCommerce (pa_color, pa_size и так далее) индексируются автоматически, а вот кастомные мета-поля нужно явно указать в настройках фильтра — какое поле индексировать и как его отображать. Мы сделали интерфейс для этого максимально простым: выбираете мета-ключ из выпадающего списка, указываете тип отображения (чекбокс, слайдер, свотч) — и система включает это поле в индекс при следующей пересборке. Но знать об этом нужно заранее, чтобы не удивляться, почему новый фильтр «Температура застывания» не показывает значения — потому что вы забыли включить его в индекс.
И последнее — производительность при очень большом количестве одновременных фильтров. Наш индекс отлично работает с пятью-десятью одновременно активными фильтрами, но если у вас каталог с тридцатью атрибутами и покупатель включил двадцать фильтров одновременно — время ответа может вырасти до ста-ста пятидесяти миллисекунд. Это всё ещё очень быстро по сравнению со стандартным WooCommerce (где такой запрос занял бы пять-десять секунд), но заметно медленнее, чем пятьдесят миллисекунд для простой фильтрации. На практике такие ситуации возникают редко — покупатели обычно используют три-пять фильтров одновременно — но если у вас специфический каталог с большим количеством технических характеристик, это стоит учитывать при проектировании интерфейса. Можно, например, разделить фильтры на «основные» (видны сразу) и «дополнительные» (раскрываются по клику), чтобы снизить вероятность одновременного использования двадцати параметров.
