Главная/Статьи/Smart Filter в Битрикс: невалидные URL и корректный 404

Smart Filter в Битрикс: невалидные URL и корректный 404

Как невалидные URL smart.filter раздувают кэш и вредят SEO. Настраиваем проверку валидности и корректную 404-обработку.

ДМ
Дмитрий Мещеряков
📅 3 июня 2026 г.📖 6 мин чтения

Умный фильтр в SEF-режиме — красивая штука с неприятным свойством: он разбирает любой набор сегментов в адресе как условия фильтра и отдаёт 200 OK практически на что угодно. Пустой список товаров, но код ответа — успешный.

Пока об этом не знают боты, всё нормально. Как только каталог начинают активно обходить, вы получаете бесконечное множество мусорных страниц в индексе, растущий кэш и потраченный краулинговый бюджет. Разберём, как научить каталог отличать осмысленный адрес от случайного.

Проблема

Фильтрация товаров в Битриксе создаёт множество URL-вариантов. Система может генерировать практически бесконечное число несуществующих URL с одинаковым содержимым, при этом все они возвращают статус 200 OK.

Как работает smart.filter

При использовании ЧПУ система формирует адреса вроде:

text
/catalog/plastikovye-konteynery/brand-is-tara_ru/

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

Источник проблемы

Можно генерировать бесконечные варианты:

text
/catalog/plastikovye-konteynery/prop_shirina-from-330/
/catalog/plastikovye-konteynery/prop_shirina-from-331/
/catalog/plastikovye-konteynery/prop_shirina-from-332/
...
/catalog/plastikovye-konteynery/prop_shirina-from-9999/

Система также обрабатывает несуществующие свойства, случайные сегменты и поломанные комбинации, отдавая при этом 200 OK.

SEO-последствия

⚠️ Важно

Проблемы без валидации:

  • Дублирование контента с разными URL
  • Размывание релевантности страниц
  • Бесполезная трата краулингового бюджета
  • Конкуренция между URL в поисковой выдаче
  • Появление низкокачественного контента в индексе

Решение

Добавляем проверку валидности URL smart.filter до рендеринга страницы. Если адрес не соответствует допустимому формату, система переводит его на штатную 404-страницу.

💡 Совет

Почему именно 404, а не noindex и не редирект.

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

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

404 — единственный честный ответ на «такого адреса нет». Робот исключает его из очереди, а при проверке до рендеринга страница даже не начинает выполняться.

Оговорка: проверять нужно до тяжёлой работы. 404 в конце обработки экономит индекс, но не экономит сервер, а под ботовой нагрузкой важно как раз второе.

Класс SmartFilterValidator

php
<?php
namespace Local\Catalog;

class SmartFilterValidator
{
    private static array $validPropertyCodes = [];
    private static ?int $catalogIblockId = null;
    
    public static function handleCatalogSmartFilterRequest(): void
    {
        $requestPath = self::getRequestPath();

        // Проверяем, что это URL каталога с фильтром
        if (!preg_match('~^/catalog/[^/]+/(.+?)/?$~u', $requestPath, $matches)) {
            return;
        }

        $tail = trim($matches[1], '/');
        if ($tail === '') {
            return;
        }

        $segments = self::splitSmartFilterSegments($tail);

        // Если есть признаки smart.filter и они невалидны — 404
        if (self::hasSmartFilterHints($segments) && !self::validateSmartFilterSegments($segments)) {
            self::processSmartFilter404();
        }
    }
    
    private static function splitSmartFilterSegments(string $tail): array
    {
        return array_filter(explode('/', $tail));
    }
    
    private static function hasSmartFilterHints(array $segments): bool
    {
        foreach ($segments as $segment) {
            if (self::isSmartFilterProp($segment)) {
                return true;
            }
        }
        return false;
    }
    
    public static function isSmartFilterProp(string $property): bool
    {
        // Проверяем формат свойства smart.filter
        return preg_match('/(?:-is-|^prop_[^\/]+|^price-[^\/]+)/u', $property) > 0;
    }
    
    private static function validateSmartFilterSegments(array $segments): bool
    {
        self::loadValidPropertyCodes();
        
        foreach ($segments as $segment) {
            if (!self::isValidSegment($segment)) {
                return false;
            }
        }
        
        return true;
    }
    
    private static function isValidSegment(string $segment): bool
    {
        // Формат: property_code-is-value или prop_code-from-N-to-M
        
        // Проверка свойств типа brand-is-value
        if (preg_match('/^([a-z0-9_]+)-is-(.+)$/i', $segment, $matches)) {
            $propertyCode = $matches[1];
            return in_array(strtoupper($propertyCode), self::$validPropertyCodes);
        }
        
        // Проверка свойств типа prop_code-from-N
        if (preg_match('/^prop_([a-z0-9_]+)-(from|to)-(\d+)$/i', $segment, $matches)) {
            $propertyCode = $matches[1];
            return in_array(strtoupper($propertyCode), self::$validPropertyCodes);
        }
        
        // Проверка цен
        if (preg_match('/^price-([a-z]+)-(from|to)-(\d+)$/i', $segment)) {
            return true;
        }
        
        return false;
    }
    
    private static function loadValidPropertyCodes(): void
    {
        if (!empty(self::$validPropertyCodes)) {
            return;
        }
        
        $catalogIblockId = self::getCatalogIblockId();
        
        $properties = \CIBlockProperty::GetList(
            [],
            [
                'IBLOCK_ID' => $catalogIblockId,
                'ACTIVE' => 'Y',
            ]
        );
        
        while ($prop = $properties->Fetch()) {
            self::$validPropertyCodes[] = strtoupper($prop['CODE']);
        }
    }
    
    private static function processSmartFilter404(): void
    {
        if (!defined('ERROR_404')) {
            define('ERROR_404', 'Y');
        }

        \Bitrix\Iblock\Component\Tools::process404(
            '',
            true,  // показать 404
            true,  // включить в статистику
            true,  // записать в лог
            '/404.php'
        );
        die();
    }
    
    private static function getRequestPath(): string
    {
        return parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ?? '';
    }
}

Подключение валидатора

В init.php:

php
<?php
use Bitrix\Main\EventManager;

$eventManager = EventManager::getInstance();

$eventManager->addEventHandler(
    'main',
    'OnBeforeProlog',
    function () {
        \Local\Catalog\SmartFilterValidator::handleCatalogSmartFilterRequest();
    }
);

Важность корректной 404-обработки

💡 Совет

Почему используем встроенный механизм Битрикса?

Метод \Bitrix\Iblock\Component\Tools::process404() корректно:

  • Отдаёт реальный HTTP 404
  • Рендерит штатную страницу ошибки
  • Не ломает шаблон и заголовки
  • Записывает событие в статистику
⚠️ Важно

Главный риск внедрения — отдать 404 на живых страницах. Валидатор слишком строгий или не знает про свойство, добавленное после его написания, — и раздел каталога, который приносил заказы, начинает отвечать «страница не найдена». Из индекса он вылетит быстро, вернётся долго.

Поэтому порядок внедрения такой:

  1. Сначала режим наблюдения. Валидатор работает, но вместо process404() пишет в лог «признал бы невалидным такой-то URL». День-два логов покажут, что реально попадает под правила.
  2. Просмотреть лог глазами. В нём не должно быть адресов из sitemap, из внутренних ссылок и из отчётов Search Console по страницам с трафиком.
  3. Только потом включать 404 — и следить за отчётом «Страницы» в панели вебмастера ближайшие недели.

Проверка после включения делается быстро:

bash
# Валидный раздел — должен быть 200
curl -o /dev/null -s -w "%{http_code}\n" https://site.ru/catalog/konteynery/

# Валидный фильтр — 200
curl -o /dev/null -s -w "%{http_code}\n" https://site.ru/catalog/konteynery/filter/brand-is-tara_ru/apply/

# Мусор — должен быть 404
curl -o /dev/null -s -w "%{http_code}\n" https://site.ru/catalog/konteynery/filter/prop_nesushchestvuet-from-777/apply/

Результаты

После внедрения решения:

МетрикаДоПосле
Ответ на невалидные URL200 OK404
Количество дублей в индексеРастётСтабильно
Краулинговый бюджетТратится впустуюИспользуется эффективно
Рост кэшаНеконтролируемыйМинимальный

Вывод

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

Что важно помнить при внедрении:

Проверка должна стоять до тяжёлой работы. Смысл не только в коде ответа, но и в том, чтобы под ботовой нагрузкой не выполнялись запросы к базе.

Валидатор — это белый список. Разрешено то, что вы явно описали: существующие свойства, допустимые значения, разумные диапазоны. Попытка перечислить «плохие» варианты обречена — их бесконечно много, в том и была исходная проблема.

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

Это половина решения. Вторая половина — ограничение кэширования для таких URL и лимит частоты для ботов. Вместе они закрывают вопрос; по отдельности каждая мера оставляет часть нагрузки.

🚀

Хотите такое же решение?

Настрою окружение под ваш проект, учту специфику инфраструктуры и обучу команду.

Обсудить проект →
Бесплатная консультация · Ответ в течение дня

Комментарии

Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.