WP Manifestindependent plugin directory
manifest / seo / woocommerce-catalog-seo

WooCommerce Catalog SEO

Keeps WooCommerce layered-navigation filter URLs out of the search index and serves a product category sitemap straight from the taxonomy

by Alex Mochulskyi · github.com/alexskybrain/woocommerce-catalog-seo

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/alexskybrain/woocommerce-catalog-seo/archive/refs/heads/main.zip

Layered navigation is the fastest way to turn one product category into several hundred URLs that all show roughly the same products. Search engines index them, split the ranking signals between them, and the category page that should rank ends up competing with its own filters.

This plugin fixes that from four directions at once. It was written for a WooCommerce shop running YITH WooCommerce Ajax Product Filter, where filtered URLs had crowded the real category pages out of the index.

Requirements

  • WordPress 6.4+
  • WooCommerce
  • PHP 8.2+
  • Yoast SEO for the canonical and Open Graph integration. Everything else works without it.

Installation

Drop the folder into wp-content/plugins/ and activate it, or into wp-content/mu-plugins/ if it should not be deactivatable. There is no settings screen; the behaviour is controlled by filters.

What it does

Filtered views are marked noindex. A request counts as filtered when it carries yith_wcan, any filter_ or query_type_ parameter, or a product_cat query argument that did not resolve to a real category archive. Directives are written through Yoast's string and array formats, through the core wp_robots array, and as a plain meta tag as a fallback, because which of the three applies depends on what the site runs.

Category pages are self-canonical. Yoast exposes the canonical in four places that can disagree: the canonical filter, the Open Graph URL, the presentation object and the WebPage schema piece. All four are set from a single source, so a filtered view never advertises itself as the canonical page.

Single-term filters are collapsed with a 301. ?yith_wcan=1&product_cat=winter-jackets and /product-category/winter-jackets/ are one page for a visitor and two for a crawler. The filter widget is taught to link to the pretty URL in the first place, and the redirect stays as a safety net for links that already exist.

The redirect is deliberately conservative: it fires only when exactly one category is selected and no other filter is active. Multi-select and mixed filters are left alone, because folding them into a single URL would lose state.

A product category sitemap is served directly from the taxonomy. Yoast builds sitemaps from its indexables table, which drifts out of sync on a large shop after imports and bulk edits — deleted categories linger, new ones never appear. Reading terms at request time removes that failure mode. The response is cached for an hour and flushed whenever a product category is created, edited or deleted.

Filters

Filter Default Purpose
woo_catalog_seo_excluded_slugs ['uncategorized'] Category slugs kept out of the sitemap. Localised shops add their own translated slug here.
woo_catalog_seo_sitemap_stylesheet Yoast's XSL when installed Stylesheet applied to the sitemap, or null for raw XML.

Architecture

src/
  Request.php    is this a category, or a filtered view of one?
  Robots.php     noindex across Yoast, core and a meta fallback
  Canonical.php  canonical, Open Graph, presentation and schema
  Redirects.php  301 single-term filters, pretty filter links
  Sitemap.php    /product_cat-sitemap.xml, cached and self-invalidating

Request holds every decision about what the current URL is, and the other four classes ask it. Keeping that judgement in one place is the point: the original version of this code repeated the same query-string inspection in five functions, and they had already started to disagree with each other.

Development

composer install
composer lint

License

GPL-2.0-or-later — see LICENSE.