URL-параметры и фасетная навигация: как не создавать бесконечные варианты страниц

URL-параметры и фасетная навигация: как не создавать бесконечные варианты страниц

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

Проблема здесь не в самом символе ? и не в том, что URL содержит параметры. Вопрос в другом: какие варианты адреса являются самостоятельными документами, какие лишь меняют представление одного набора данных, а какие вообще не должны создавать новые crawlable URL.

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

Как возникает URL-space explosion

Представим каталог, где есть четыре фильтра:

color = 8 значений
size = 6 значений
brand = 20 значений
material = 5 значений

Если система позволяет комбинировать их свободно, число потенциальных состояний растёт не линейно. При этом URL могут отличаться ещё и порядком параметров:

/catalog?color=black&size=m
/catalog?size=m&color=black

Для пользователя это может быть один и тот же список товаров. Для crawler это два разных URL, пока система явно не показывает обратное.

К комбинациям фильтров часто добавляются:

  • sort=price;
  • page=2;
  • view=grid;
  • utm_source=...;
  • ref=...;
  • session ID;
  • служебные параметры CMS;
  • несколько значений одного фильтра.

В итоге один набор объектов доступен через множество адресов, а часть комбинаций может вообще возвращать пустой результат.

Сначала разделите параметры по функции

Не все query parameters равнозначны. Для инженерного решения удобно сначала разложить их по роли.

Тип параметра Пример Что меняет Основной вопрос
Фильтр ?color=black Набор объектов Нужна ли эта комбинация как отдельная поисковая страница?
Сортировка ?sort=price_asc Порядок тех же объектов Есть ли причина считать это отдельным документом?
Представление ?view=grid UI без существенного изменения содержания Нужен ли отдельный URL вообще?
Пагинация ?page=3 Подмножество списка Как обеспечивается последовательный discovery элементов?
Трекинг ?utm_source=... Обычно не меняет основной контент Почему этот параметр должен участвовать в идентичности документа?
Сессия ?sessionid=... Состояние пользователя Можно ли хранить его вне URL?

Google рекомендует по возможности сокращать число ненужных параметров, а session IDs не помещать в URL, если их можно хранить, например, в cookie. Это не косметика: чем больше параметров, которые не меняют полезное содержимое, тем больше дублирующих адресов может создавать система.

У фасетной навигации есть два принципиально разных режима

Решение об индексации фасета

Перед настройкой нужно ответить на один вопрос:

Нужны ли конкретные фасетные URL как самостоятельные страницы в поиске?

Дальше архитектура расходится.

Режим 1. Фильтры нужны только пользователю

Например, магазин разрешает отфильтровать каталог по десяти параметрам, но ни одна комбинация не должна жить как отдельная поисковая посадочная страница.

В таком случае задача — не «канонизировать миллион URL», а не создавать для crawler бесконечную поверхность обхода. В документации по faceted navigation Google рекомендует ограничивать crawling таких URL, если они не нужны для поиска. Для фильтров, которые существуют только как интерфейсное состояние, возможна и реализация через URL fragments — при условии, что эти состояния действительно не должны становиться отдельными страницами для Google Search.

Режим 2. Часть фасетов имеет самостоятельную ценность

Иногда комбинация фильтра соответствует реальной пользовательской задаче:

/notebooks?brand=lenovo
/notebooks?screen=14
/notebooks?brand=lenovo&screen=14

Но даже здесь нельзя автоматически объявлять индексируемыми все возможные комбинации. Нужно заранее определить допустимый набор URL, который имеет:

  • устойчивый спрос или понятную навигационную функцию;
  • достаточно самостоятельный набор объектов;
  • предсказуемый Title/H1 и основной контент;
  • стабильный URL;
  • внутренние ссылки;
  • понятное canonical-поведение;
  • корректный HTTP-ответ.

То есть фасет превращается в indexable page не потому, что технически существует, а потому что сайт сознательно признал его отдельным документом.

Не используйте canonical как универсальный выключатель фасетов

Распространённая схема выглядит так:

любой URL с фильтрами
→ rel="canonical" на базовую категорию

Она может быть оправдана для дублирующих или очень похожих вариантов, но плохо работает как универсальное правило.

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

Кроме того, Google рассматривает rel="canonical" как сигнал нормализации, а не как директиву управления crawling. В документации по фасетной навигации прямо отмечено, что canonical может со временем уменьшить crawl volume неканонических вариантов, но для предотвращения обхода бесконечного пространства он менее эффективен, чем решения, специально управляющие crawling.

Более подробно различие между похожими URL, дубликатами и основной версией документа разобрано в материале про canonical URL и дубли страниц.

robots.txt управляет обходом, а не выбирает каноническую страницу

Если фасетные URL не нужны в поиске и создают огромное crawl space, Google рекомендует рассмотреть запрет их обхода через robots.txt.

Но важно не смешивать две разные задачи:

robots.txt
→ может ограничить запрос crawler к URL

rel="canonical"
→ сообщает предпочтительную версию среди одинаковых или похожих страниц

Если URL запрещён в robots.txt, Googlebot не получает HTML страницы и не может прочитать размещённый в нём canonical. Поэтому схема «закроем дубли в robots.txt, а внутри каждого оставим canonical» логически конфликтует: crawler не видит второй сигнал.

Robots-правила здесь нужно проектировать как crawl policy. Canonicalization — отдельный слой.

Различия robots canonical noindex sitemap

noindex тоже не решает проблему бесконечного обхода

Другой вариант:

все фасетные URL открыты для crawler
+
на каждом meta robots noindex

Такой URL должен быть запрошен и обработан, чтобы поисковая система увидела noindex. Если комбинаций сотни тысяч, основной расход на crawling уже произошёл.

Поэтому noindex может быть правильным инструментом для отдельных документов, которые доступны роботу, но не должны оставаться в индексе. Он не является хорошей заменой архитектурному контролю бесконечного URL-space.

Порядок параметров должен быть стабильным

Если фасетные URL разрешено обходить, один и тот же набор фильтров не должен собираться десятком способов.

Например, выберите одно правило:

brand → color → size → sort

и придерживайтесь его:

/catalog?brand=acme&color=black&size=m

а не генерируйте одновременно:

/catalog?color=black&brand=acme&size=m
/catalog?size=m&brand=acme&color=black
/catalog?brand=acme&size=m&color=black

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

Стабильность URL нужна не ради красоты. Она уменьшает число технически разных адресов для одного состояния.

Не допускайте дублирующиеся и бессмысленные фильтры

Система должна нормализовать или отклонять конструкции вроде:

?color=black&color=black
?size=m&size=xl&size=m
?brand=acme&brand=acme

Ещё хуже — комбинации, которые невозможно выполнить по модели данных:

?category=laptops&engine=diesel

Google рекомендует для бессмысленных, дублирующих или несуществующих комбинаций возвращать корректный 404, а не бесконечно создавать пустые страницы с HTTP 200.

Пустой результат должен иметь осмысленный HTTP-статус

Типичный анти-паттерн:

GET /catalog?brand=unknown&color=purple
→ HTTP 200
→ "Ничего не найдено"

Если такая комбинация не представляет реальную страницу и результатов для неё нет, Google рекомендует отвечать 404 Not Found. То же касается несуществующих pagination URL и заведомо некорректных комбинаций фильтров.

При этом не нужно отправлять пользователя редиректом на одну общую страницу «ничего не найдено». Сервер может показать понятное сообщение непосредственно на исходном URL и вернуть корректный 404.

Сортировка обычно не создаёт новый документ

Параметры вроде:

?sort=price_asc
?sort=price_desc
?sort=popular
?sort=newest

часто меняют только порядок одних и тех же объектов. В таком случае indexable copies обычно не дают отдельной поисковой ценности.

Но техническое решение зависит от реализации:

  • можно не создавать crawlable links на сортировки;
  • можно нормализовать URL;
  • можно использовать canonical на основную версию, если страницы действительно почти идентичны;
  • можно ограничить crawling параметра, если он порождает большое число ненужных адресов.

Главное — не оставлять сортировку как бесконтрольный генератор новых URL.

Tracking-параметры не должны становиться отдельной архитектурой сайта

Метки кампаний полезны аналитике:

?utm_source=newsletter&utm_campaign=autumn

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

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

Если вопрос уже не в параметрах, а в том, как поисковый робот вообще обнаруживает URL через ссылочный граф, полезно перейти к статье о внутренних ссылках и orphan pages.

Внутренние ссылки определяют, какие фасеты crawler вообще увидит

Фасет может существовать технически, но crawler узнаёт о нём только через какой-то discovery channel: внутреннюю ссылку, sitemap, внешний backlink, сохранённый URL или другой источник.

Поэтому генерация HTML-ссылок на каждый доступный фильтр — отдельное архитектурное решение.

Если категория выводит:

цвет × размер × бренд × материал × доставка × цена

и каждый выбранный фильтр генерирует ссылки на все оставшиеся комбинации, crawl graph начинает разрастаться вместе с URL-space.

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

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

Так управление discovery начинается в шаблоне страницы, а не только в robots.txt после того, как проблема уже появилась.

Sitemap не должен перечислять все комбинации параметров

XML Sitemap — это ещё один способ сообщить поисковой системе о URL, но он не предназначен для выгрузки каждого технически возможного состояния фильтра.

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

самостоятельный indexable facet
→ может попасть в sitemap

сортировка / трекинг / технический дубль
→ в sitemap не нужен

бесконечная комбинация пользовательских фильтров
→ не превращать sitemap в генератор URL-space

Сам факт присутствия URL в XML-файле не делает его автоматически индексируемым. Подробнее эта граница разобрана в статье о возможностях и ограничениях XML Sitemap.

Indexable facet должен быть настоящей страницей

Если вы решили, что определённый фильтр нужен в поиске, недостаточно снять noindex. URL должен вести себя как самостоятельный документ.

Проверьте:

  • уникальна ли задача страницы;
  • есть ли стабильный URL;
  • содержимое заметно отличается от базовой категории;
  • есть ли товары, статьи или события, соответствующие фильтру;
  • не исчезает ли список при каждом небольшом изменении данных;
  • есть ли нормальные внутренние ссылки;
  • self-canonical ли у страницы, если она действительно самостоятельна;
  • возвращается ли HTTP 200 только для существующего состояния;
  • не создаёт ли этот URL ещё десятки ненужных вариантов.

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

Не все комбинации даже полезных фасетов должны индексироваться

Допустим, по одному фильтру существуют хорошие страницы:

ноутбуки Lenovo
ноутбуки 14 дюймов
ноутбуки с 32 ГБ памяти

Из этого не следует, что комбинация:

Lenovo + 14 дюймов + 32 ГБ + серый + вес до 1,4 кг + доставка сегодня

тоже заслуживает отдельного индексируемого URL.

Чем глубже комбинация, тем выше риск получить:

  • слишком узкий набор объектов;
  • почти пустые страницы;
  • дублирование соседних фасетов;
  • нестабильное содержание;
  • огромное число редко полезных URL.

Поэтому indexable whitelist лучше проектировать явно, а не делать все комбинации открытыми по умолчанию.

Что делать с параметрами цены

Диапазонные фильтры особенно опасны:

?price_from=1000&price_to=2000
?price_from=1001&price_to=2001
?price_from=1002&price_to=2002

Технически пользователь может задать почти бесконечное число комбинаций. Если каждая из них создаёт crawlable URL, пространство становится практически неограниченным.

Для поиска обычно разумнее использовать заранее определённые диапазоны, если они вообще нужны как отдельные страницы:

до 5 000
5 000–10 000
10 000–20 000

а произвольные пользовательские значения не превращать в indexable URL.

Пагинацию не нужно автоматически смешивать с фасетами

URL:

/catalog?brand=acme&page=4

решает другую задачу, чем:

/catalog?brand=acme&color=black

Первый продолжает просмотр одного списка, второй меняет сам набор объектов.

Если система смешивает pagination, sorting и filtering без строгих правил, быстро возникают конструкции вроде:

?brand=acme&color=black&sort=price&page=97&view=compact

Каждый слой нужно проектировать отдельно: какие значения допустимы, какие URL существуют, что возвращает сервер за пределами списка и какие варианты распространяются ссылками.

Проверяйте не только HTML, но и реальное поведение URL

Диагностика фасетной навигации должна начинаться с HTTP и маршрутизации, а не с одного тега в <head>.

Для набора тестовых URL проверьте:

  1. HTTP-статус;
  2. финальный URL после redirect chain;
  3. rel="canonical";
  4. meta robots и X-Robots-Tag;
  5. доступность в robots.txt;
  6. наличие внутренних ссылок на вариант;
  7. попадание в sitemap;
  8. основной контент и список объектов;
  9. поведение пустой и некорректной комбинации;
  10. стабильность порядка параметров.

Полезно брать не один «правильный» URL, а маленькую матрицу:

базовая категория
один разрешённый фильтр
два фильтра
сортировка
tracking parameter
пустой фильтр
дублирующий параметр
несуществующая page=N
другой порядок тех же параметров

Такая выборка быстро показывает, есть ли у системы единая политика или каждый edge case живёт отдельно.

Логи сервера показывают настоящий масштаб проблемы

Если есть доступ к access logs, можно увидеть, какие parameter URLs реально запрашивает crawler.

Ищите:

  • параметры с огромным числом уникальных значений;
  • одни и те же фильтры в разном порядке;
  • глубокие комбинации из четырёх-пяти фасетов;
  • бесконечную пагинацию;
  • пустые результаты с HTTP 200;
  • служебные и tracking-параметры, на которые ведут внутренние ссылки;
  • URL, которых нет в интерфейсе, но crawler продолжает обходить их из старого графа.

Логи особенно полезны после изменения правил: по ним видно не только конфигурацию, но и фактическое поведение crawler во времени.

Типичные ошибочные схемы

Схема Почему слабая Что проверить вместо этого
Все фильтры index,follow URL-space растёт без ограничений Какие фасеты действительно нужны как страницы
Все фильтры canonical на категорию Смешиваются реальные страницы и дубли Сначала классифицировать состояния
Все фильтры noindex Crawler всё равно должен их запрашивать, чтобы увидеть noindex Нужен ли обход этого пространства вообще
Все фильтры Disallow Может закрыть полезные indexable facets и скрыть HTML-сигналы Точная crawl policy по типам параметров
Все URL помещены в sitemap Sitemap начинает рекламировать технические варианты Только URL, предназначенные для поиска
Внутренние ссылки генерируют все комбинации Crawl graph взрывается ещё до анализа canonical Какие состояния должны быть crawlable links
Пустые результаты возвращают 200 Создаются бесконечные пустые документы Корректный 404 для несуществующей комбинации

Практическая схема принятия решения

Параметр меняет основной набор контента?
│
├─ Нет
│  └─ Не создавать отдельную поисковую страницу
│     → чистый canonical URL / не распространять параметр / crawl control
│
└─ Да
   │
   ├─ Комбинация нужна как самостоятельный search landing?
   │  │
   │  ├─ Да
   │  │  → стабильный URL
   │  │  → HTTP 200
   │  │  → самостоятельный контент
   │  │  → self-canonical
   │  │  → внутренние ссылки
   │  │  → при необходимости sitemap
   │  │
   │  └─ Нет
   │     → не превращать её в бесконечный crawlable URL-space
   │
   └─ Пустая / невозможная комбинация
      → HTTP 404

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

Итог

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

Для фасетной навигации сначала нужен whitelist полезных состояний и единые правила маршрутизации. После этого уже настраиваются crawling, canonical, внутренние ссылки, sitemap и HTTP-ответы. Если сделать наоборот и пытаться исправить бесконечный URL-space одним тегом rel="canonical", архитектурная проблема останется.

Рабочая последовательность выглядит так: классифицировать параметры → определить indexable facets → ограничить лишний discovery → нормализовать URL → проверить canonical → вернуть 404 для невозможных комбинаций → согласовать внутренние ссылки и sitemap → проверить фактический crawl по логам.

Источники