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

URL: https://dmeshcheryakov.ru/blog/bitrix-smartfilter-404/
Раздел: 1С-Битрикс
Теги: 1С-Битрикс, SEO, Smart Filter, Каталог
Опубликовано: 2026-06-03
Обновлено: 2026-09-07
Автор: Дмитрий Мещеряков (https://dmeshcheryakov.ru)

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

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

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

## Почему smart.filter отдаёт 200 на несуществующих URL

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

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

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

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

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

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

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

```
/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 в поисковой выдаче
- Появление низкокачественного контента в индексе

## Как научить каталог отдавать 404 на мусорных адресах

Добавляем проверку валидности 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/
```

## Результаты

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

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

## Вывод

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

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

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

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

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

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