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

Если фильтры товаров в WooCommerce начинают тормозить на каталоге с большим количеством товаров, проблема обычно не в самом AJAX как таковом, а в том, что каждый клик запускает дорогой запрос к базе, пересчитывает количество товаров, тянет лишние метаполя и иногда ещё конфликтует с кэшем темы или плагина. На небольшом магазине это почти незаметно, но на каталоге с вариациями, атрибутами и несколькими таксономиями задержка быстро становится видимой.

Ниже — рабочий разбор именно для сценария «фильтр есть, но он медленный». Без абстрактных советов вроде «поставьте кэш» — сначала нужно понять, что именно грузит запрос, а потом уже резать лишнее.

Как понять, что тормозит именно AJAX-фильтр

Симптомы обычно похожи: список товаров обновляется с задержкой, крутится индикатор загрузки, а в Network видно долгий запрос к admin-ajax.php или к REST-эндпоинту плагина фильтрации. Важно не путать это с медленной отрисовкой карточек товаров на фронтенде: если основной HTML страницы открывается быстро, а зависает только фильтрация, узкое место почти всегда в серверной части.

Что проверить в первую очередь

  • какой именно запрос уходит при смене фильтра: admin-ajax.php, REST API или отдельный endpoint плагина;
  • сколько времени занимает TTFB именно у этого запроса;
  • есть ли в запросе пересчёт количества товаров по нескольким атрибутам и категориям одновременно;
  • не отключён ли объектный кэш, если фильтр часто делает повторяющиеся запросы;
  • не добавляет ли тема или сторонний плагин лишние JOIN к запросу товаров.

Если у вас есть доступ к Query Monitor, это самый быстрый способ увидеть, какие SQL-запросы выполняются при фильтрации. Если нет — хотя бы откройте DevTools, вкладку Network, и сравните время ответа фильтра с обычной загрузкой страницы категории.

Почему AJAX-фильтры WooCommerce становятся медленными

Чаще всего причина одна из трёх: тяжёлый SQL по wp_postmeta, слишком частый пересчёт количества товаров в фильтрах и отсутствие кэширования одинаковых комбинаций параметров. На практике всё это может происходить одновременно.

Типичный сценарий: фильтр по цене, бренду и размеру строит запрос с несколькими tax_query и meta_query. Если товаров много, а индексы в базе не помогают, MySQL начинает работать заметно дольше. Добавьте сюда сортировку по популярности или рейтингу, и задержка становится ещё выше.

Когда проблема не в WooCommerce

Иногда виноват не сам магазин, а внешний фильтр-плагин или тема, которая перегружает запросы на странице архива. Например, плагин может каждый раз пересчитывать количество товаров для всех доступных значений фильтра, хотя пользователю нужен только текущий результат. В таком случае ускорение достигается не «оптимизацией WooCommerce вообще», а отключением лишних пересчётов и сокращением набора данных в ответе.

Пошаговое решение: уменьшаем стоимость одного запроса

Ниже — практический порядок действий. Он подходит, если фильтр уже работает, но отвечает медленно.

1. Уберите лишние данные из ответа

Если ваш AJAX-обработчик возвращает весь HTML карточек товара вместе с лишними блоками, проверьте, нельзя ли отдавать только список товаров и пагинацию. Чем меньше HTML генерируется на каждый запрос, тем быстрее ответ.

Если фильтр реализован своим кодом, полезно отдавать только нужную часть шаблона:

add_action('wp_ajax_my_shop_filter', 'my_shop_filter_ajax');
add_action('wp_ajax_nopriv_my_shop_filter', 'my_shop_filter_ajax');

function my_shop_filter_ajax() {
    check_ajax_referer('my_shop_filter_nonce', 'nonce');

    $args = array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => 12,
        'paged'          => max(1, absint($_POST['page'] ?? 1)),
        'fields'         => 'ids',
        'no_found_rows'  => false,
    );

    $query = new WP_Query($args);

    ob_start();
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            wc_get_template_part('content', 'product');
        }
    }
    wp_reset_postdata();

    wp_send_json_success(array(
        'html' => ob_get_clean(),
        'found' => (int) $query->found_posts,
        'max_pages' => (int) $query->max_num_pages,
    ));
}

Здесь важный момент: fields => 'ids' уменьшает объём данных, которые WordPress вытаскивает из базы, а шаблон карточки рендерится только для нужных товаров. Это не магия, но на больших каталогах помогает.

2. Не пересчитывайте то, что можно закэшировать

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

function my_shop_filter_cache_key(array $params): string {
    ksort($params);
    return 'my_shop_filter_' . md5(wp_json_encode($params));
}

function my_shop_get_filtered_products(array $params): array {
    $cache_key = my_shop_filter_cache_key($params);
    $cached = get_transient($cache_key);

    if ($cached !== false) {
        return $cached;
    }

    $query = new WP_Query(array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        'posts_per_page' => 12,
        'paged'          => max(1, absint($params['page'] ?? 1)),
        'tax_query'      => array(),
        'meta_query'     => array(),
    ));

    $result = array(
        'ids' => wp_list_pluck($query->posts, 'ID'),
        'found' => (int) $query->found_posts,
        'max_pages' => (int) $query->max_num_pages,
    );

    set_transient($cache_key, $result, 5 * MINUTE_IN_SECONDS);

    return $result;
}

Кэшировать нужно аккуратно: если у вас часто меняются остатки, цены или видимость товаров, слишком длинный TTL даст устаревшие данные. Для фильтров обычно достаточно короткого окна, а при обновлении товара кэш можно сбрасывать через хук сохранения товара.

3. Сократите количество условий в запросе

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

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

Сравнение подходов: плагин, код или гибрид

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

ПодходЧто даётОграничения
Только плагинБыстрый запуск, меньше разработкиСложнее убрать лишние запросы и пересчёты
Только кодПолный контроль над SQL и кэшемНужно поддерживать самостоятельно
ГибридМожно оставить UI плагина и оптимизировать серверную частьНужно аккуратно проверять совместимость

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

Проверка результата после внедрения

Оптимизация имеет смысл только если её можно измерить. После изменений проверьте не «кажется быстрее», а конкретные признаки.

  • вкладка Network: время ответа AJAX-запроса стало ниже;
  • в Query Monitor: уменьшилось число SQL-запросов или их суммарное время;
  • на медленном соединении фильтр перестал «подвисать» после клика;
  • пагинация и счётчики товаров продолжают показывать корректные значения;
  • при смене фильтров не ломается сортировка и не сбрасываются выбранные параметры.

Если вы добавляли transient-кэш, отдельно проверьте сценарий обновления товара: измените цену или наличие, сохраните товар и убедитесь, что фильтр не отдаёт старую версию результата. Для этого удобно открыть каталог в инкогнито и сравнить поведение до и после сохранения.

Частые ошибки и как их исправить

Кэшируют всё подряд и получают устаревшие фильтры

Ошибка типичная: кэш ставят на слишком долгий срок, а потом жалуются, что фильтр показывает старые цены или наличие. Решение простое — уменьшить TTL и сбрасывать кэш при сохранении товара, а не ждать, пока он сам истечёт.

Отключают no_found_rows там, где нужна пагинация

Если вам нужны общее количество товаров и страницы, нельзя бездумно включать no_found_rows => true. Это ускоряет запрос, но ломает пагинацию и счётчики. Используйте это только там, где количество страниц не требуется.

Фильтр строит запрос через метаполя вместо таксономий

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

Не проверяют конфликт с темой

Некоторые темы добавляют свои обёртки, сортировки и дополнительные запросы на архиве товаров. Если после оптимизации ничего не изменилось, временно переключитесь на стандартную тему или отключите лишние модули темы и сравните время ответа.

Безопасность и производительность: что не стоит пропускать

AJAX-фильтр — это не только про скорость. Если вы пишете свой обработчик, обязательно проверяйте nonce через check_ajax_referer() и приводите входные параметры к нужным типам. Это не защита от всех проблем, но базовый уровень, который не стоит игнорировать.

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

Если каталог большой, имеет смысл дополнительно посмотреть на объектный кэш и на то, как часто фильтр повторяет одинаковые запросы. В некоторых проектах именно короткий transient-кэш даёт более заметный эффект, чем попытка «ускорить весь WordPress сразу».

Когда нужен не только фильтр, но и общая чистка сайта от лишних дублей, мета и служебного мусора, можно посмотреть в сторону Clearfy Pro, но только как на инструмент для точечной оптимизации, а не как на замену нормальной диагностики.

Что делать, если фильтр всё ещё медленный

Если после сокращения запроса и кэширования задержка осталась, значит, узкое место глубже: база данных, медленный хостинг, слишком тяжёлые сортировки или сторонний фильтр-плагин, который не даёт контролировать SQL. В этом случае полезно сравнить время ответа на одном и том же наборе фильтров до и после отключения плагина фильтрации. Если разница большая, проблема не в WooCommerce, а в реализации фильтра.

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

Как отключить XML-RPC в WordPress без поломки синхронизации и внешних сервисов
21.08.2026
Как создать lazy load в WordPress своими руками для ускорения сайта
19.11.2025
Оптимизация обработки REST API в WordPress для малоизвестных сценариев
04.03.2026
WooCommerce: решение проблемы замедления страницы поиска при большом количестве товаров
08.07.2026
Как создать эффективный кэш в WordPress своими руками
11.11.2025