Оптимізація SEO для фільтруємих сторінок каталогу (faceted navigation SEO)
Фасетна навігація — фільтрація каталогу по атрибутах: колір, розмір, бренд, ціна, рейтинг. Кожна комбінація фільтрів генерує унікальний URL. На каталозі з 1000 товарів з 10 атрибутів — математично можливи мільйони URL. Google намагається их всіх обійти, витрачає весь crawl budget, ні одна сторінка не отримує достатньо сигналів, сайт сповільнюється в індексації.
Діагностика масштабу проблеми
Спочатку потрібно зрозуміти скільки унікальних URL генерує поточна фасетка:
# Краулинг через Screaming Frog з фіксацією параметрів
# Configuration → Spider → Crawl Behaviour → Enable JavaScript
# Альтернатива — sitemap аналіз
curl -s https://example.com/sitemap.xml | grep '<loc>' | wc -l
# GSC: Coverage report — Excluded → Crawled - currently not indexed
# Якщо там тисячі URL з параметрами — проблема підтверджена
Класифікація URL фільтрів по цінності
Не всі фільтри одинаково бесполезні:
Цінні (потребує індексація):
-
/catalog/laptops/?brand=apple→ "ноутбуки apple" -
/catalog/dresses/?color=black&length=midi→ "чорне міді платье"
Технічний мусор (закрити від індексації):
-
/catalog/?sort=price_asc— сортування -
/catalog/?page=2&color=red— комбінація пагінації з фільтром -
/catalog/?min_price=0&max_price=99999— цінові діапазони без спросу -
/catalog/?color=red&color=blue— мультивибір одного атрибуту
Технічні рішення
Метод 1: robots.txt — закрити параметри
Грубий інструмент, використовувати для параметрів без SEO-цінності:
User-agent: *
Disallow: /catalog/*?*sort=
Disallow: /catalog/*?*page=
Disallow: /catalog/*?*min_price=
Проблема: Google може не дотримуватися wildcard Disallow стабільно.
Метод 2: noindex + follow для нежелатедних комбінацій
Більш надійний варіант — серверна логіка додає noindex залежно від параметрів:
// Laravel Middleware
class FacetedSeoMiddleware
{
private const NOINDEX_PARAMS = ['sort', 'page', 'min_price', 'max_price'];
private const ALLOWED_SINGLE_FACETS = ['brand', 'color', 'size'];
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$queryParams = $request->query();
$paramKeys = array_keys($queryParams);
$shouldNoindex = in_array(true, array_map(
fn($k) => in_array($k, $this->NOINDEX_PARAMS),
$paramKeys
)) || count(array_intersect($paramKeys, $this->ALLOWED_SINGLE_FACETS)) > 2;
if ($shouldNoindex) {
$response->headers->set('X-Robots-Tag', 'noindex, follow');
}
return $response;
}
}
Метод 3: Canonical для дублюючих комбінацій
Якщо /catalog/?color=red&brand=nike та /catalog/?brand=nike&color=red генерують однаковий контент:
function buildCanonicalUrl(Request $request): string
{
$params = $request->query();
ksort($params); // Сортуємо по алфавіту
$allowedFacets = ['brand', 'color', 'size'];
$filteredParams = array_filter(
$params,
fn($key) => in_array($key, $allowedFacets),
ARRAY_FILTER_USE_KEY
);
$baseUrl = $request->url();
return $filteredParams
? $baseUrl . '?' . http_build_query($filteredParams)
: $baseUrl;
}
Метод 4: Окремі SEO-сторінки для цінних комбінацій
Найпотужніший підхід: створити чисті slug-based сторінки для високочастотних комбінацій:
/catalog/laptops/apple/ → "ноутбуки apple"
/catalog/dresses/black/midi/ → "чорне міді платье"
Хлібні крошки та внутрішня перелинківка
Фасетні сторінки мусять отримувати внутрішні посилання:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Каталог", "item": "https://example.com/catalog/"},
{"@type": "ListItem", "position": 2, "name": "Ноутбуки", "item": "https://example.com/catalog/laptops/"},
{"@type": "ListItem", "position": 3, "name": "Apple", "item": "https://example.com/catalog/laptops/apple/"}
]
}
</script>
Моніторинг після впровадження
def get_indexed_faceted_urls(site_url: str, credentials) -> list:
service = build('searchconsole', 'v1', credentials=credentials)
response = service.searchanalytics().query(
siteUrl=site_url,
body={
'startDate': '2024-01-01',
'endDate': '2024-03-31',
'dimensions': ['page'],
'dimensionFilterGroups': [{
'filters': [{
'dimension': 'page',
'operator': 'contains',
'expression': '?'
}]
}]
}
).execute()
return [row['keys'][0] for row in response.get('rows', [])]
Метрика успіху: зменшення кількості краулед URL з параметрами через 4–8 тижнів. Слідкувати в GSC Coverage.
Тривалість
Аудит фасетної структури, класифікація параметрів, розроблення стратегії — 3–5 днів. Технічна реалізація (middleware noindex, нормалізація canonical, SEO-сторінки) — 5–10 днів. Моніторинг результату — 4–6 тижнів після впровадження.







