SEO

Grid Panda includes a full SEO subsystem: server-side rendering for crawlability, canonical URL management, robots meta directives, clean filter URLs, structured data (JSON-LD), and sitemap integration. All features are configurable without touching code.

SEO Architecture Overview

SeoManager

Central coordinator. Hooks into wp_head to output canonical, robots, Open Graph, and meta title/description tags on filtered pages. Includes a reentrancy guard to prevent recursive title resolution.

UrlRewriter

Transforms clean filter URLs (/shop/filter/color-red/) into fx_ query params at the WordPress request-parsing level (do_parse_request hook). Does not require rewrite rules in .htaccess.

FilterParamHandler

Reads fx_ query params from the request, builds WP_Query clauses, and injects them into the grid's query at the wp action (priority 5). This makes filtered pages render server-side — Googlebot sees full content.

SitemapProvider

Generates indexable filter URLs for the XML sitemap. Integrates with WordPress core sitemaps, Yoast SEO, and Rank Math. Only includes facet types and depths configured as indexable.

StructuredData

Outputs Schema.org ItemList JSON-LD for grid pages. Automatically uses Product schema for WooCommerce products and BlogPosting/Article for other post types.

Server-Side Rendering for Crawlability

The most important SEO feature in Grid Panda is server-side rendering of filtered states. When Googlebot or any crawler requests a filtered URL, Grid Panda renders the correct posts in the initial HTML response — no JavaScript execution required.

This works via FilterParamHandler, which hooks into wp at priority 5 (before the shortcode renders), parses all fx_ query parameters from the request, and injects the resulting WP_Query clauses into the grid query:

// Request URL:
GET /shop/?fx_color=red&fx_brand=apple

// FilterParamHandler parses:
$selections = [
  'color' => ['red'],
  'brand' => ['apple'],
]

// Injects into WP_Query:
tax_query: [
  { taxonomy: 'pa_color', field: 'slug', terms: ['red'] },
  { taxonomy: 'pa_brand', field: 'slug', terms: ['apple'] }
]

// Grid shortcode renders filtered products server-side
// Googlebot sees the actual filtered product list in HTML

The same mechanism applies to clean URLs — UrlRewriter converts/shop/filter/color-red/brand-apple/into fx_ params before FilterParamHandler runs.

Clean Filter URLs

By default, Grid Panda uses query string URLs (?fx_color=red). Enable Clean URLs in Grid Panda → Settings → SEO to use permalink-style URLs:

ModeURL Format
Query string (default)/shop/?fx_color=red&fx_brand=apple
Clean URLs (enabled)/shop/filter/color-red/brand-apple/
Multi-value (+ separator)/shop/filter/color-red+blue/
Numeric range/shop/filter/price-25-200/
Date range/shop/filter/date-2024-01-01-2024-12-31/
Paginated/shop/filter/color-red/page-2/

Clean URL Format

Each filter segment follows the pattern {facet-slug}-{value}. Multiple facets are separate path segments. Multiple values within one facet use + as separator. Range values use - between min and max.

Requirements: Clean URLs require WordPress pretty permalinks (any structure other than Plain). Go to Settings → Permalinks and click Save Changes after enabling to flush rewrite rules.

Custom Base URL per Facet

// Override the base URL for sitemap filter URLs per facet
add_filter( 'gridpanda/seo/sitemap_base_url', function( $base_url, $slug, $value ) {
    if ( 'product_cat' === $slug ) {
        return home_url( '/shop/' );
    }
    return $base_url;
}, 10, 3 );

Canonical URLs

Grid Panda outputs a <link rel="canonical"> tag for every filtered page. The canonical is the clean URL version of the current filter state — this prevents parameter-order variations from creating duplicate content:

<!-- These two URLs have the same canonical: -->
/shop/?fx_brand=apple&fx_color=red
/shop/?fx_color=red&fx_brand=apple

<link rel="canonical" href="/shop/filter/brand-apple/color-red/" />
<!-- (with clean URLs enabled; sorts slugs alphabetically) -->

Customise the canonical via the gridpanda/seo/canonical filter.

Robots Meta Directives

Grid Panda adds a <meta name="robots"> tag to filtered pages. Two strategies are available (Grid Panda → Settings → SEO → Robots Strategy):

noindex

All filtered pages receive noindex, follow. Only the unfiltered base page is indexed. Safe default — prevents index bloat from filter combinations.

canonical_only

No noindex is added. Google should consolidate duplicate filtered variants via the canonical tag. Use when you want specific filter combinations to rank (requires careful sitemap configuration).

// Selectively allow specific filter combinations to be indexed
add_filter( 'gridpanda/seo/robots', function( $directives, $active_filters, $page ) {
    // Allow single-category filter pages to be indexed
    if ( count( $active_filters ) === 1 && isset( $active_filters['product_cat'] ) ) {
        return []; // No robots restrictions
    }
    return $directives;
}, 10, 3 );

Meta Title & Description Templates

Customise the page title and meta description for filtered pages using template strings with placeholders. Configure in Grid Panda → Settings → SEO:

PlaceholderResolves To
{filter_label}The first active facet's name (e.g. 'Color')
{filter_value}The first active filter value (e.g. 'Red')
{site_name}WordPress site name from get_bloginfo('name')
{page_title}The base page's title without filter context

Example templates:

Title: {page_title}{filter_label}: {filter_value} | {site_name}Description: Browse our {filter_label} {filter_value} products. Find what you're looking for with Grid Panda.

Override programmatically via the gridpanda/seo/meta_title andgridpanda/seo/meta_description filters.

Open Graph

When Enable Open Graph is on (default: true), Grid Panda outputs Open Graph meta tags on filtered pages for correct social sharing previews:

<meta property="og:title"       content="Red Products | MyShop" />
<meta property="og:description" content="Browse our Color Red products..." />
<meta property="og:url"         content="https://myshop.com/shop/filter/color-red/" />
<meta property="og:type"        content="website" />

Structured Data (JSON-LD)

Grid Panda outputs a Schema.org ItemList JSON-LD script tag on grid pages. Each post in the current result set is added as a ListItem. Post type determines the inner schema:

WooCommerce products → Product schema

  • name, url, image
  • description (from excerpt)
  • offers (Offer: price, currency, availability, url)
  • aggregateRating (if reviews exist)

Other post types → BlogPosting / WebPage

  • name, url, image
  • datePublished, dateModified
  • author (Person: name)
  • description (from excerpt)
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "ItemList",
  "name": "Products — Color: Red",
  "numberOfItems": 12,
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "url": "https://myshop.com/product/red-widget/",
      "item": {
        "@type": "Product",
        "name": "Red Widget",
        "url": "https://myshop.com/product/red-widget/",
        "image": "https://myshop.com/wp-content/uploads/red-widget.jpg",
        "offers": {
          "@type": "Offer",
          "price": "29.99",
          "priceCurrency": "USD",
          "availability": "https://schema.org/InStock"
        }
      }
    }
  ]
}
</script>

Customise the structured data via thegridpanda/seo/structured_data filter.

XML Sitemap Integration

Enable Sitemap Integration in Grid Panda → Settings → SEO to include indexable filter URLs in the XML sitemap. Grid Panda integrates with three sitemap systems:

WordPress Core Sitemaps (WP 5.5+)

wp_sitemaps_add_provider filter

Grid Panda registers as a sitemap provider. Filter URLs appear under /wp-sitemap.xml.

Yoast SEO

wpseo_sitemap_index + wpseo_do_sitemap_gridpanda

Grid Panda adds a <sitemap> entry to the index at /gridpanda-sitemap.xml. The separate sitemap lists all indexable filter URLs with lastmod and changefreq.

Rank Math

rank_math/sitemap/extra_urls

Grid Panda injects filter URLs into Rank Math's post sitemap via the extra_urls action.

Indexable URL Generation

Only single-facet-value URLs are included in the sitemap by default (depth=1). Multi-facet combinations grow exponentially and would consume crawl budget — they are excluded unless thegridpanda_seo_max_indexable_depth is set above 2.

Only facets with seo_indexable: true in their config are included. Range, date_range, and rating facets are always excluded (non-enumerable values).

// Customise which URLs appear in the sitemap
add_filter( 'gridpanda/seo/sitemap_urls', function( $urls ) {
    // Remove URLs with very low estimated traffic
    return array_filter( $urls, function( $url ) {
        return ! str_contains( $url['url'], '/color-black/' );
    } );
} );

SEO Settings Reference

Option KeyDefaultDescription
gridpanda_seo_clean_urls_enabledfalseEnable /filter/slug-value/ clean URLs
gridpanda_seo_max_indexable_depth2Max facet depth in sitemap (1 = single-value URLs only)
gridpanda_seo_robots_strategy'noindex'Robots handling: noindex or canonical_only
gridpanda_seo_meta_title_template''Template for filtered page <title>
gridpanda_seo_meta_description_template''Template for meta description
gridpanda_seo_enable_open_graphtrueOutput og:title, og:description, og:url tags
gridpanda_seo_enable_sitemapfalseInclude filter URLs in XML sitemaps