E-commerce specific SEO rules for product pages, category pages, pricing, availability, faceted navigation, and B2B considerations. Use when building or modifying product listings, category pages, shopping cart, or checkout.
Follow these rules when working on product pages, category pages, and shopping features.
For detailed e-commerce SEO patterns, see reference.md.
Every product page should have:
{Model} {Product Name} | {Brand}
Include model number — B2B buyers search by model.
A common gap in e-commerce sites is missing price and availability in Product schema, which prevents rich results (price, stock status, ratings) from appearing in Google.
Required for rich results (per Google): , , , , . Strongly recommended: , , . Provide if available; if not, provide + for product identification. Also consider and for merchant listing eligibility. See the seo-structured-data skill for full details.
nameimage[]offers.priceoffers.priceCurrencyoffers.availabilityskubranddescriptiongtinbrandmpnshippingDetailshasMerchantReturnPolicyDisplay specs prominently and include them in structured data via additionalProperty. Example spec fields:
Visible breadcrumb trail reflecting the site hierarchy (e.g. Home > Category > Subcategory > Product Name) with matching BreadcrumbList schema (see seo-structured-data skill).
{Category Name} - Buy Online | {Brand}const categorySchema = {
"@type": "CollectionPage",
name: category.name,
description: category.description,
url: `${siteConfig.url}/${locale}/categories/${category.slug}`, // adapt path to your project
mainEntity: {
"@type": "ItemList",
itemListElement: products.map((product, index) => ({
"@type": "ListItem",
position: index + 1,
url: `${siteConfig.url}/${locale}/products/${product.id}/${product.slug}`,
name: product.name,
})),
},
};
Faceted navigation (filters by price, specs, features) can create duplicate or thin content if not handled correctly. Google's guidance is selective, not blanket — useful filter combinations that match real search intent should remain indexable.
Keep useful filter pages indexable — if a filter combination represents a genuine product category that users search for (e.g. /categories/headphones?type=wireless), let it be indexed. These pages have unique value.
Noindex granular multi-filter combinations — ?power=500w&color=red&weight=5kg creates thousands of thin pages with little search value. Use noindex, follow so crawlers can still discover products through these pages.
Set the canonical to the base category for granular filters — all low-value filter combinations should have their canonical pointing to the base:
/en-GB/categories/widgets?power=500w&color=red&weight=5kg
→ canonical: /en-GB/categories/widgets
Use noindex, follow (not noindex, nofollow) on filtered pages — allows crawlers to discover products through filtered pages without indexing the filter combination itself
// App Router example — in Pages Router, set robots via next/head
export async function generateMetadata({ searchParams }: Props): Promise<Metadata> {
// Determine if this is a useful, searchable filter (e.g. single broad category)
// vs a granular multi-filter combo with little search value
const filterKeys = Object.keys(searchParams).filter(k => k !== 'sort');
const isGranularFilter = filterKeys.length > 1;
return {
robots: isGranularFilter
? { index: false, follow: true } // noindex granular filter combos
: { index: true, follow: true }, // index useful single-filter views
};
}
For B2B e-commerce sites, pricing requires special handling:
offers.price and offers.priceCurrency in Product schemaprice entirely from schema rather than showing 0 or a placeholder. Still include availability.offers.priceSpecification with no price, or omit offers blockoffers.eligibleCustomerType: "https://schema.org/Business"const offers = product.priceVisible
? {
"@type": "Offer",
price: product.price,
priceCurrency: getCurrencyForLocale(locale),
availability: `https://schema.org/${product.inStock ? 'InStock' : 'OutOfStock'}`,
seller: { "@type": "Organization", name: siteConfig.brandName },
}
: {
"@type": "Offer",
availability: `https://schema.org/${product.inStock ? 'InStock' : 'OutOfStock'}`,
seller: { "@type": "Organization", name: siteConfig.brandName },
eligibleCustomerType: "https://schema.org/Business",
};
For products with variants (same product line in different sizes, power ratings, colors):
isVariantOf to link variants to a parent product groupisSimilarTo for related products in the same categoryconst variantSchema = {
"@type": "Product",
name: "Widget Pro 500W",
isVariantOf: {
"@type": "ProductGroup",
name: "Widget Pro Series",
url: `${siteConfig.url}/${locale}/categories/widgets`,
},
};
If the site offers product bundles, these need special SEO treatment: