An ecommerce SEO audit is a review of which of a store’s URLs search engines should crawl and index, and where ranking authority leaks between the pages that sell and the pages that never will. A regular SEO audit asks what’s broken on a page: title tags, site speed, a redirect chain. An ecommerce SEO site audit asks the question the page-level view can’t see — across the millions of URLs a store spins up, which ones should Google crawl, which deserve a place in the index of their own, and where is equity draining into pages that will never sell anything.
Because the scale is the whole problem. A store with 10,000 products doesn’t have 10,000 URLs. It has a few million. Twenty filters, three sort orders, pagination, session parameters: multiply them out and a mid-sized catalog generates a URL space Botify once measured, on a site with under 200,000 products, at more than half a billion pages a crawler could reach. Almost none of them should be in Google’s index. That gap, between what a store can generate and what deserves to rank, is the whole subject of the audit — and it’s roughly where the ranking guides for this term stop.
One boundary before the checks. This is how I find the damage. Fixing it — the rebuild, the URL architecture, the routing rules — is its own discipline, and I’ll mark where a finding turns into a build decision rather than blur the two. An audit that mixes them ends up a 60-page document nobody acts on. The job here is diagnosis, prioritized.
Scope of an ecommerce SEO audit: taxonomy, crawl, index, intent
A store is a system with four moving parts a content site doesn’t have:
- a taxonomy that branches into categories, subcategories and attributes, and renders as product listing pages (PLPs) and product detail pages (PDPs);
- a crawl-control layer deciding what bots reach;
- an index-control layer deciding what survives to rank;
- and underneath all of it, commercial intent — whether a given page maps to something a person searches for with a wallet open.
A blog has pages. A store has all of that, and the audit is mostly about the seams where the four disagree: the category that’s crawlable but shouldn’t be indexed, the product that’s indexed but reachable by no link, the filter that matches demand but was blocked by a rule written for a different filter.
The checks below aren’t a checklist walked top to bottom. They’re the places those four layers tear, ordered by how much revenue usually leaks through each. The checklist version, for people who came here for one, is further down — as a decision sheet, one row per URL class.
Crawling and indexing: index decisions by URL class
Crawling is whether Googlebot can reach a URL. Indexing is whether that URL earns an independent place in the results. The gap between the two is where ecommerce sites rot.
So I run every URL class through one question: does this deserve to be in the index at all. Take the classes one at a time — top-level categories, subcategories, PLPs, PDPs, then the machinery underneath: variant URLs, facet combinations, sort parameters, pagination, internal search results. Each lands in one of five buckets: crawl and index it, canonicalize it to a parent, noindex it, block it outright, or leave it a pure UX state Google never sees. The audit’s job is to find every place the site answered that indexation question wrong — indexing what should stay hidden, hiding what should rank.
Each of those buckets has a readout in Search Console, and the mismatches show up as specific statuses. A canonical Google didn’t accept reads as Duplicate, Google chose different canonical than user — the canonical tag is a hint, and that status is the list of places Google overruled it. Pages you blocked after they’d been indexed sit under Indexed, though blocked by robots.txt. Facet junk that got crawled and rejected piles up in Crawled – currently not indexed. I read those three reasons by URL template before anything else. Together they’re the map of where the site’s stated intent and Google’s decision diverge.
XML sitemap vs. index coverage
The XML sitemap is the site’s own statement of what it published, so it gets read against the index the same way. What the platform puts in it varies, and the variation is itself a finding.
Shopify writes sitemap_products_N.xml, sitemap_collections_N.xml and the rest itself, lists every published product and collection, and gives you no control beyond unpublishing or the seo.hidden metafield — so a product you noindexed through a theme edit is still in the sitemap. Magento 2 generates one sitemap per store view, and with “Use Categories Path for Product URLs” on, the same product carries a URL under every category it belongs to: the sitemap advertises one, the site links to several. WooCommerce under Yoast or Rank Math ships product tag and attribute archives by default, which on a store with 400 tags is 400 thin listing pages submitted for indexing on purpose.
A sitemap URL that carries a noindex, or canonicalizes elsewhere, contradicts the sitemap in writing, and each contradiction has a status that names it:
| What the sitemap does | How it reads in Search Console |
|---|---|
| Lists a URL you noindexed | Excluded by 'noindex' tag |
| Lists a URL that canonicalizes elsewhere | Duplicate, submitted URL not selected as canonical |
The sitemap should contain the first bucket and nothing else. When it doesn’t, the share of sitemap URLs Google indexed, per template, is the fastest single number for how far the site’s intent sits from Google’s decision.
Index bloat and over-noindexing
Bloat is the answer most stores get wrong, and it runs one direction far more often than the other. Color crossed with size crossed with brand becomes forty thousand thin, near-identical pages, every one indexable, every one competing with its own parent category for the same query. The reverse failure is rarer and more expensive: a valuable page — a facet matching demand, “waterproof hiking boots,” a legitimate collection — sitting behind a noindex someone applied with a broad brush, invisible in the index it earned a place in.
To read this at all I have to confirm what’s in the index, class by class — not what the site intends, what Google holds. The site: operator gives an estimate that can be off by an order of magnitude on a large store, and the Search Console report caps its export at a thousand rows per reason, which on a site with two hundred thousand excluded URLs shows you half a percent of the problem. The URL Inspection API is exact but capped at 2,000 requests a day per property, so on a large store you sample by template — two hundred URLs from each class — rather than enumerate. I run a bulk index check across Google and Bing that way, URL template by URL template, reading index status per URL, so the picture is “of this class of page, here’s the share that’s indexed,” set against what the site thinks it published. Bing’s Webmaster Tools exposes the same per-URL inspection with a looser quota, and on a store the two engines rarely disagree on categories and disagree constantly on facets, which is its own signal about how thin the facets are.

noindex, a form that only 301s, a facet whose canonical points elsewhere.The order matters when you’re cleaning up an existing mess. Lift the disallow first, let Google crawl the noindex and drop the pages, then put the block back. On a set of a hundred thousand facet URLs that takes weeks, because a noindex only takes effect on the next crawl of each URL and facet URLs are exactly the ones Googlebot visits least. The Removals tool is the bridge: it hides the URLs for about six months, and a prefix removal on /collections/*?filter covers the whole class in one request while the noindex does the permanent work.
Crawl budget: server log analysis by URL type
“Millions of URLs” is an abstraction until you watch where Googlebot spends its day, and then it turns into money. Google’s own guidance names two tiers where crawl budget starts to matter: sites over a million unique pages with content that changes weekly, or sites over ten thousand pages with content that changes daily — plus any site, whatever its size, where Search Console shows a swelling pile of Discovered – currently not indexed. Google calls those numbers rough estimates, not thresholds. Every store I audit crosses the second tier the moment faceted filters switch on. The catalog is 5,000 products; the URL space the bots see is five million.
How the 2026 crawl-budget guidance changed for stores
Google rewrote that guidance on July 22, 2026, and two of the additions matter for stores. Every site now starts on the same conservative default crawl capacity limit, which Google raises only as the server proves it can take more. And the capacity is shared across all of Google’s crawlers per hostname — Googlebot, AdsBot, the image and Shopping fetchers each have their own demand, but they draw from one pool. The documentation now says it outright: AdsBot wants more when you run dynamic ad targets, Google Shopping wants more when you have products in Merchant Center feeds. On a store running heavy Shopping campaigns the ads crawler is spending the same budget the organic crawler needs, and a slow server throttles both. The same rewrite added HTTP caching as a lever: return 304 Not Modified for a page that hasn’t changed and the fetch costs almost nothing. Most storefronts don’t — PLPs and PDPs ship without a usable Last-Modified or ETag, so every Googlebot visit is a full 200 with a full render. The share of 304s in the Googlebot log lines is a number I now read on every store, and on most it’s zero. Gary Illyes added the other half in 2025: what costs crawl isn’t page count but expensive requests, so a category template with a slow database call behind it does more damage than a hundred thousand static URLs.
THE NUMBER THAT LANDS WITH CLIENTS
Screaming Frog’s analysis puts about 35% of a typical ecommerce site’s crawl budget on faceted URLs that return no SEO value — a third of everything Google will spend on you, poured into pages you’d never want ranked.
Facet, sort and pagination URL patterns by platform
I read that split by segmenting the crawl with regex by URL type. The patterns are platform-specific:
| Platform | Filters / facets | Sort | Pagination | Duplicates and junk |
|---|---|---|---|---|
| Shopify | filter.v.option.color=, filter.p.vendor=, filter.v.price.gte= | ?sort_by= | ?page= | ?variant=ID on every PDP; /collections/x/products/y for each product |
| Magento 2 | ?color=…&price=…&cat= (layered navigation) | ?product_list_order=, ?product_list_limit= | ?p= | category-path product URLs (/category/product.html) |
| Salesforce Commerce Cloud | ?prefn1=&prefv1= pairs | ?srule= | ?start=&sz= | ?dwvar_ variant parameters |
| WooCommerce | ?filter_color=, ?min_price=&max_price= | ?orderby= | /page/2/ | ?add-to-cart=ID; product tag and attribute archives |
| BigCommerce | theme- and app-dependent | ?sort= | ?page= | — |
Then I set that against the server logs to see where Googlebot’s hits land per type. Before I trust the logs I verify the Googlebot hits against Google’s published ranges — the googlebot.json file under developers.google.com, or a reverse DNS to googlebot.com — because fake Googlebot traffic is common enough on stores to distort the split, and I cross-check with the Crawl stats report in Search Console, which breaks hits down by purpose and, separately, by Googlebot type. The type breakdown is where the shared capacity shows: on a store running Shopping campaigns, AdsBot and the Shopping fetcher sit next to Googlebot Smartphone in that chart, and every request they make is one the organic crawler doesn’t get. A mature store whose crawl is still mostly “discovery” rather than “refresh” is telling you the crawler keeps finding URLs it didn’t know about, and on a catalog that hasn’t grown, those are facets. Two more cuts from the same logs: the share of Googlebot hits landing on 3xx and 404, which on a store with lazy redirect rules runs into double digits, and the share of hits on URLs with two or more parameters, which is the facet grid by another name. The pattern is almost always the same. The crawler grinds through facet grids and sort permutations and deep pagination while new products wait weeks to be seen.
Multi-store setups, hreflang and AI crawlers
A multi-store setup multiplies the whole space again. Each locale or currency version of the catalog regenerates every facet and variant URL, and without reciprocal hreflang the copies compete with each other as duplicates instead of reading as one product sold in three markets. Shopify Markets adds hreflang itself on folder-based markets (/en-ca/, /fr-ca/) but not across separate stores on separate domains; Magento store views on different base URLs get nothing unless a module writes it. I check hreflang per template rather than per URL: return links present on every copy, an x-default set, and the canonical on each market’s page pointing to itself. The common mistake is the canonical on every regional copy pointing back to the flagship store, which hands every market’s rankings to one country and leaves the others crawled for nothing — it reads in Search Console as Alternate page with proper canonical tag stacking up on the non-flagship properties.
There’s a newer cost on top of the old one. AI crawlers now take a sizeable share of the bot hits on a store and fetch far less efficiently than Googlebot: Cloudflare’s research with ETH Zurich found that over 90% of what they request is unique long-tail URLs rather than repeat hits on popular pages, so CDN caching doesn’t absorb it — and the long tail of a store is the facet grid. The filter combinations wasting one company’s crawl budget are now wasting several. That doesn’t change the diagnosis. It raises the price of leaving it unfixed.
Orphan products and broken pagination
There’s a related leak the crawl view surfaces: orphan products. A product page reachable by no internal link is invisible to the crawler’s sense of importance, because nothing on the site votes for it — it sits there technically indexable and never ranked. Orphans come in three shapes:
- The dropped product. One removed from its category or never linked at launch.
- The pagination failure. A category running on infinite scroll or lazy-load with no HTML pagination underneath — no
<a href>links a crawler can follow to page two and beyond. Google hasn’t usedrel="prev"andrel="next"as a pagination signal since 2019, so the only thing that links page two is a link. - The inventory setting. WooCommerce’s “hide out of stock items from the catalog” removes a sold-out product from every listing while its URL keeps returning 200 — the product is orphaned the moment stock hits zero and re-linked when it’s replenished, and the crawler sees a page that appears and disappears from the graph.
Googlebot does render JavaScript, though, and with a tall viewport, so lazy-loading built on IntersectionObserver often fires and the products appear. What doesn’t fire is anything tied to a scroll event or a “Load more” click. The visitor scrolls and JavaScript fetches more products; Googlebot doesn’t scroll and doesn’t click, so it sees the first screen of the listing and stops. Everything in the catalog tail behind that scroll is orphaned at once. This shows up plainly in a crawl: Screaming Frog with JavaScript rendering on reaches the first page of the listing, finds no href to page two, and stops — so the products it found come back far short of the catalog count the store thinks is linked. The gap between the two numbers is the tail that’s invisible because the only path to it runs through an interaction the crawler never performs. I read the internal-link graph for both shapes: the products nothing points at, and the deep listings nothing reaches because the pagination isn’t there. Crawl depth comes out of the same graph — a product six clicks from the homepage, reachable only through page nine of a listing, is linked in theory and orphaned in practice. On a store with 24 products per page and 1,200 products in a category, the last product is fifty pages deep before any filter is applied; that’s why the head categories need subcategory links in the body, not only in a mega-menu the crawler already has.
Out-of-stock and discontinued product pages
This is ecommerce-specific and the ranking pages barely touch it, which is strange, because it’s one of the most common ways a store bleeds authority without anyone noticing. Products go out of stock. Products get discontinued. Both are inventory events, and on most stores both are handled by whoever built the platform, with no thought to what happens to a page’s accumulated ranking equity when the product behind it vanishes.
The failures have a shape, and each platform has a default:
- Shopify keeps a sold-out product at 200 until someone unpublishes it, at which point it’s a 404 with no 410 available without an app.
- Magento disables the product and returns 404, and the old URL rewrite stays in the table pointing at nothing.
- WooCommerce trashes it to a 404 or, with the catalog-visibility setting, leaves it live and unlinked.
From there the equity dies one of three ways. A discontinued product’s URL starts returning a hard 404, and every link that pointed at it — internal, external, the ranking history baked in — dead-ends. Or the platform’s “helpful” default kicks in and every dead product 301-redirects to the homepage, which Google’s documentation explicitly classes as a soft 404 and ignores, so the equity evaporates the same way, just more politely. Or the worst version: a “temporarily out of stock” page for a product discontinued a year ago, still returning 200, still indexed, still pulling a click that lands on nothing anyone can buy.
What the audit does is separate the temporary from the permanent, because they route differently and the site almost always treats them the same. A temporary stockout isn’t an indexation problem. The page should stay, the equity should hold, and the structured data should say so: an Offer with availability set to OutOfStock or BackOrder keeps the page eligible for merchant listings while telling Google the truth. Schema.org also has Discontinued, PreOrder, SoldOut and LimitedAvailability as values, and almost no storefront emits any of them — the theme maps stock to InStock or OutOfStock and nothing else, so the one value that would tell Google a product is gone for good never gets sent. A page that says “out of stock” in the copy and InStock in the schema loses the rich result and, on a Merchant Center product, gets the item disapproved for availability mismatch. How long counts as temporary depends on the category’s normal restock cycle, not on a fixed number of days. Past that, treat it as permanent.
To find them I run a custom extraction in the crawl on whatever DOM element carries the stock state — the “Sold out” button label, the availability meta, the JSON-LD Offer — cross it against the inventory export, and pull the referring domains and internal-link counts for every dead URL. That’s the number that says how much equity is on the table. The audit finds the leak: how many dead products, returning what status codes, bleeding equity where. What the correct routing is for each one — replacement model, parent category, or a clean 410 — is a build decision, and it lives in the rebuild, not the diagnosis. On 404 versus 410: Google treats them nearly alike, 410 just drops from the index a little faster. Pick the routing first; the code follows.
The same referring-domain pull tells me where the store’s external links land, and it’s the only backlink work the audit does. The pattern repeats across stores: the homepage and the blog hold most of the referring domains, the categories that carry the revenue have almost none, and a measurable share of the links point at products that were discontinued two seasons ago and now 404 — earned equity landing on a dead end. I map links by destination template and status code, so the store sees how much of what it already earned reaches a page that can still rank. That’s a number to read before anyone proposes buying more.
Faceted navigation and variant URLs
One product in twelve colors and eight sizes can, if the platform is naïve, become ninety-six URLs — every color-size pair its own crawlable, indexable page, all near-identical, all competing with each other and the parent product for one query. Facets do the same a level up: a store with 1,000 products and five filter dimensions can generate over 100,000 URLs, most of them thin, some accidentally competing with the very category they filter.
Single-page vs. multi-page variants and ProductGroup markup
Google gave variants a formal structure in February 2024 with ProductGroup markup — a parent carrying productGroupID and variesBy, a hasVariant array of Product items each with its own Offer — and it’s useful for the audit because it makes the site pick a model. Single-page: one canonical URL for the whole group, every variant preselectable through a query parameter that swaps image, price and availability without a page load; Shopify’s ?variant=ID is this model, and its theme canonical points every variant at the bare product URL. Multi-page: each variant on its own URL with self-contained markup, and only when that variant carries its own demand — a phone case in one color has none, a mattress in one size does. The finding I see most often is neither. The URLs say multi-page — ninety-six indexable paths — while the schema says single-page, or the color pages are self-canonical and the size pages canonicalize to them, or neither the canonical tag nor the markup agrees with what Search Console reports as the chosen canonical. When the URL model, the canonical and the structured data tell three different stories, Google picks one, and it’s rarely the one the store wanted.
Which facets to index: search demand
The question for each one is whether it earns an independent place in the index, and the dividing line is search demand. A filter combination people search — “black maxi dresses,” “women’s trail running shoes” — has its own intent, enough products and a clean landing, so it deserves its own indexable page. A combination nobody searches — a random three-filter stack, a temporary stock state, a sort order, a session parameter — earns nothing, and left indexable it just splits signal and drains crawl budget. Facets and variants take three of the five buckets from earlier: index it (demand, distinct intent), canonicalize it to the parent (it overlaps an existing page), or noindex and block it (no demand, thin, or a pure UX state). The platforms don’t make this easy in either direction. Shopify canonicalizes every filtered collection URL to the bare collection, which handles the junk and makes the one facet you want indexed impossible to index without a separate collection. Magento canonicalizes filtered pages to the category only if “Use Canonical Link Meta Tag For Categories” is on, and the layered-navigation extensions that make filter URLs pretty and indexable — the Amasty and Mirasvit class — make all of them indexable, so a store that wanted three landing pages gets three thousand.
Google’s own faceted-navigation guidance from December 2024 sets the conditions for the facets you do keep, and I check the kept ones against it: parameters joined with a standard ampersand, filter order fixed so the same set never produces two URLs, and a 404 for any combination with no products in it rather than an empty grid served as 200. For everything else it’s a robots block — Disallow: /*?*filter. or Disallow: /*&sort= patterns, one line per parameter family — or a URL fragment, not a nofollow. A nofollow only works if every anchor to that URL carries it, and one unmarked link in a sidebar template undoes the rest. And noindex is not a crawl-budget tool; Googlebot has to fetch the page to read it.
This matters more than it used to because of how Google handles pages it can’t tell apart. It reads a page as a position in meaning-space rather than a string of words, and two pages built from the same template land almost on top of each other there even when the visible words differ. Then it collapses them, or picks one and buries the rest. Google’s June 2026 spam update went after scaled, templated pages — networks of location pages identical but for the city name were losing rankings within the first two days of the rollout — and a faceted grid churning out near-identical filter pages is the same shape. Eyeballing duplicate text won’t find it. I look for clusters of URLs too close in meaning to justify separate pages, and for the mismatch between what’s canonicalized and what’s indexed. The self-competing pairs give it away: a parent category and one of its own filtered states both ranking, badly, for one term — in Search Console that’s one query with two landing pages trading positions week to week, neither climbing.
Thin and duplicate content on product and category pages
Everyone agrees ecommerce has a thin-content problem, and almost everyone is wrong about what it is. The reflex is: product pages run the manufacturer’s boilerplate, identical across every store selling the same SKU, so that’s duplicate content and Google penalizes it. There is no such penalty — Google’s own John Mueller has said it plainly, and Google’s documentation treats ordinary duplication as normal, not a spam violation. Google deduplicates instead, which is worse for you. It groups the pages carrying that same paragraph — the manufacturer, a big marketplace, your store — and shows one, usually the most established source. Your copied page isn’t punished; it never ranks, and from the store’s side those two outcomes look identical. In Search Console it doesn’t even look like a content problem: the URL sits under Duplicate, Google chose different canonical than user with the canonical pointing at a domain you don’t own.
Product pages: entity completeness and structured data
So the audit question on a product page is whether it deserves original treatment at all, and that turns on demand. A long-tail product nobody searches doesn’t need rewriting; canonicalize it and spend nothing on it. A product people search for, where you’re losing the canonical to a marketplace carrying the same supplier text, is where original content earns its cost — and “content” here isn’t a longer paragraph. A product page in 2026 ranks on the completeness of the product entity: variants declared properly, images and video the store shot itself, specifications, reviews and Q&A that weren’t bought, shipping and return terms that read like a business rather than a legal template. Google splits product rich results in two, and the audit checks which one each template qualifies for: merchant listings need price, priceCurrency and availability in the Offer, with shippingDetails and hasMerchantReturnPolicy for the shipping and returns annotations; product snippets need a review, aggregateRating or offers. A page with neither valid gets a plain blue link next to competitors with stars and a price. A GTIN that fails the GS1 check digit is worse than no GTIN — Merchant Center disapproves the item, and the Product markup on the page fails validation for the same field. That completeness is also what gets a product cited in AI shopping answers — attribute-rich structured data (GTIN, MPN, brand, ratings, full offers) shows up in AI product recommendations far more often than the bare minimum. A 150-word page that answers what a buyer needs to know beats a 600-word wall of supplier prose.
Category pages: assortment and editorial context
Category pages fail differently, and they matter more. Across roughly thirty retail sites in a seoClarity and JumpFly study, category pages ranked for 19% more keywords than product pages and drew 413% more traffic from them, and they outperformed on 27 of the 30 sites — because a category catches the broad, high-volume search before the shopper has picked a SKU. A category is thin when it’s just a product grid with a title, or a grid with a wall of keyword text dumped underneath to hit a word count, which Google reads as noise and grows less sure what the page is even about; length has nothing to do with it. What a strong category needs is editorial context a shopper uses and the right assortment on the shelf: a category that reads well but lists the wrong products is thin in a way no word count catches. I check the assortment against the query: a “waterproof hiking boots” category whose first row is three non-waterproof models on sale is answering a merchandising rule, not the search. Below the head category runs the demand ladder — subcategory, then the filter and tag pages that each catch a slice of long-tail intent (“waterproof,” “under $100,” “for wide feet”). Each rung that matches search demand is a page worth having; each one that doesn’t is a page to canonicalize away. Finding which rungs have demand and no page, and which pages exist with no demand behind them, is the audit.
Keyword-to-URL mapping and intent gaps
The last differentiator is demand-side, and it asks a question a crawler can’t. For every way a person shops your category — by attribute, by use case, by comparison, by the problem they’re solving — is there a page built to catch that intent, indexable and ranking? Or is the demand landing somewhere it shouldn’t, or nowhere?
The findings are consistent across stores:
- A high-value intent with search volume and no page targeting it.
- An informational blog post ranking where a category page should be, catching the visitor in the wrong context to sell to.
- Two pages splitting one intent, each too weak to win because the store cannibalized itself.
What surfaces those is mapping demand against the existing URL set — and the demand side of that map takes more than one keyword tool. Three sources say more than Ahrefs and Semrush do. Search Console queries with impressions where the landing page is a product or a blog post instead of a category — filter the Performance report to pages containing /blog/ or /products/, then sort queries by impressions, and the commercial terms leaking onto the wrong template are the top of the list; the Search appearance filter for “Merchant listings” and “Product snippets” shows which pages are earning the rich result and which are stuck as blue links. On-site search logs, and specifically the zero-result searches, which are the cleanest signal there is of a shelf people expect and the store doesn’t have — in GA4 that’s the view_search_results event with search_term, cross-referenced against the results count the theme passes, or the platform’s own report on Shopify and Magento. And marketplace autocomplete, Amazon’s especially, which reflects how people phrase purchase intent before any SEO tool has caught up.
Internal links to category pages
There’s an internal-linking dimension the intent map exposes. Even when the right category page exists, the site often fails to point at it — a strong commercial page starved of internal links while the blog hoards the equity everyone links to by habit. The map has a second column: pages that exist but nothing votes for. The crawl gives the count; the useful version is inlinks per template divided by revenue per template, which on most stores puts the blog at the top and the money categories at the bottom.
Product feed audit: Merchant Center and AI shopping
There’s a place your products live that isn’t your website. For AI shopping, and increasingly for Google’s own shopping surfaces, the product data that gets recommended doesn’t come off your page. It comes from your feed.
WHERE THE RECOMMENDATIONS COME FROM
A study by Tom Wells this year mapped where the products in ChatGPT’s shopping carousel originate: across more than 43,000 products, 83% matched Google’s top-40 organic Shopping results. Those come from the Merchant Center feed, which now supplies a whole ecosystem — Google’s shopping panels, AI Overviews, ChatGPT’s carousel, Perplexity’s product answers — all reading from the same structured file most stores haven’t touched since they set up paid Shopping.
And there’s now a second feed alongside it. OpenAI’s product feed spec, under its Agentic Commerce Protocol, lets a merchant push a catalog straight to ChatGPT — CSV or JSON, refreshed as often as every fifteen minutes, price and availability required, variants grouped under a group_id so a color or size can be surfaced on its own. Google’s Universal Commerce Protocol is the parallel on its side. Onboarding is still by approval, but the direction is set: a store’s presence in AI shopping is becoming a data-pipeline question, and the pipelines can disagree with each other and with the site. What I don’t have yet is a number for how much of the ChatGPT carousel the direct feed will take from the Merchant Center path. The 83% study was run before merchants could push their own catalog, and I’d expect it to look different in a year.
Feed vs. page: price, availability, identifiers, crawler access
That makes the feed an index in its own right, with its own version of every failure the website has. So I audit it like one, and this part I do by hand, opening the feed against the site. Three checks carry most of the findings:
- Does the feed match the page? Same price, same availability — or have they drifted, which in Merchant Center means disapproval and on the AI surfaces means the product drops from consideration.
- Are the attributes complete on the SKUs that matter? Merchant Center allows 150 characters in the title and shows about 70, so a title that front-loads the brand and buries the product type and variant is invisible in the carousel even when it’s valid; identifiers missing on your highest-revenue products fail the item outright, the way one feed audit found 85% of titles too short for AI systems to place them.
- Can the AI crawlers even reach the catalog? That check has two layers now. A
robots.txtrule written years ago may still lock them out. And a store behind Cloudflare may be blocking them at the edge without knowing it — Cloudflare has blocked AI crawlers by default for new customers since July 2025, sorobots.txtcan say Allow while the CDN returns 403 toOAI-SearchBot. You see it in the logs as a 403 rate by user agent that the site’s own rules don’t explain.
Technical SEO: rendering, Core Web Vitals, schema, canonicals
Everything above is where I spend the audit, because it’s where stores differ and where the money is. The standard technical SEO layer still gets checked, but the top ten cover it well and I rarely find surprises, so I move fast:
- Rendering. Are the products in the DOM, or does the page ship an empty shell that only fills after JavaScript hydrates — because if it’s the latter, the crawler may be indexing a blank page. I check that with URL inspection in Search Console, “view crawled page,” against the rendered DOM; if the product grid is in one and not the other, Google is indexing the template and not the products. Headless storefronts on Hydrogen, Next.js or Vue Storefront are where this shows up, and the tell is a crawled HTML tab that’s mostly script tags.
- Core Web Vitals, read per template, not one site-wide score, because a PDP and a PLP fail differently and the average hides both — and CrUX only reports per origin or per URL, so per-template means sampling twenty URLs from each class through the CrUX API or reading your own RUM. The thresholds are 2.5 seconds LCP, 200 milliseconds INP, 0.1 CLS, and the pattern on stores is consistent: PLPs fail LCP on the hero image and the grid, PDPs fail INP on the variant selector — on Shopify themes it refetches the product section on every option change — and whatever the tag manager loads under it. Speed also feeds the crawl: a slow response means fewer pages crawled per visit, so on a million-URL store it feeds straight back into the crawl-budget leak, and third-party scripts and tag managers are usually where the time goes.
- Product schema — Product, Offer, AggregateRating, BreadcrumbList, GTIN and MPN — present and valid, since it’s what makes a product eligible for rich results at all.
- Canonical hygiene across the URL space.
- Mobile, which for most stores is simply the primary version now.
Ecommerce SEO audit checklist: the triage sheet
This is the checklist, for anyone who searched for one. It’s fourteen rows because each row is a decision, and the default in the middle column is what I expect to find; the audit is the list of places the store departs from it without a reason.
| URL class | Default decision | Where the mismatch shows | Owner |
|---|---|---|---|
| Top-level category | Index | GSC Pages report; sitemap coverage per template | SEO |
| Subcategory / PLP | Index if it has its own demand | Duplicate, Google chose different canonical; queries landing on the wrong page | SEO |
| Facet with search demand | Index; fixed parameter order; 404 when empty | Two URLs for one filter set; empty grids returning 200 | Engineering |
| Facet without demand | Canonicalize to parent, or block | Crawled – currently not indexed by template; log share by URL type | Engineering |
| Sort, session, tracking parameters | Block, or keep as fragment | Crawl stats “discovery” share on a static catalog | Engineering |
| Pagination pages | Crawlable via HTML links; self-canonical | Crawler product count below catalog count | Engineering |
| PDP with demand | Index; original entity data; valid Product / Offer | Rich-result eligibility per template; canonical lost to a marketplace | Content |
| Long-tail PDP with supplier copy | Index, spend nothing on it | Deduplicated to a marketplace source | Content |
| Variant URLs | One model: single-page or multi-page, matching the ProductGroup markup | URL, canonical and schema telling three stories | Engineering |
| Out of stock, temporary | Keep; availability OutOfStock / BackOrder in schema | Copy and schema disagree; Merchant Center availability-mismatch disapprovals | Content / feed |
| Discontinued | Route the equity, then 410 | Referring domains and internal links on 404 / 200-but-dead URLs | Redirect owner |
| Internal search results | Block | Indexed /search?q= URLs; log share | Engineering |
| Regional / currency copies | Self-canonical, reciprocal hreflang, x-default | Canonical pointing at the flagship store; Alternate page with proper canonical tag | Engineering |
| Product feed | Matches the page: price, availability, identifiers; reachable by OAI-SearchBot | Merchant Center disapprovals; 403s by user agent in the logs | Feed owner |
Audit report: prioritization by revenue and owner
A flat list of 137 issues sorted by severity is a technical export, not an audit, and it’s worse than useless because it launders a crawler’s sense of “critical” into yours. A tool flags a facet-bloat warning on a dead category as critical and an indexation gap on your best-selling category as medium, and the list hands you the dead category first — it has no idea which page touches revenue.
The output inverts that. Severity gets weighted by commercial value: a medium problem on the page that drives revenue outranks a critical one on a page nobody should reach. Every finding carries one number — the URLs it touches multiplied by the share of organic revenue that template earns, with the revenue share pulled from the GA4 landing-page report grouped by URL pattern — so two findings can be compared without arguing about adjectives. Findings sort by impact against effort, so the store knows what to do Monday, not just what’s wrong. And every finding names its owner — facet rules are engineering, duplicate content is content, equity routing belongs to whoever owns redirects. Leave that off and nobody knows whose desk it lands on, so it lands on none.
The report is three things, done in order, each with the revenue it touches. The issue count is an appendix.
Frequently asked questions
How often should a large ecommerce store be audited?
For a catalog over 10,000 URLs, a full audit once or twice a year is enough, because the structural decisions — index buckets, facet rules, variant model — don’t change month to month. What does change fast is the log file: crawl allocation and parameter spikes want checking monthly, and that’s a monitoring job, not a re-audit. If the catalog or the platform is stable and nothing in Search Console is moving, an audit sooner than that mostly finds the same list again.
Should faceted filter URLs be blocked in robots.txt or set to noindex?
Depends on which problem you’re solving, and the two aren’t interchangeable. A facet with no search demand that isn’t indexed yet: block it in robots.txt, so Googlebot never spends crawl on it. A thin facet that’s already in the index: noindex it, because a block can’t remove what it can’t crawl — Googlebot has to fetch the page to read the tag. Never both on the same URL. The one case for keeping a facet indexable is real search demand behind it, and then it’s neither blocked nor noindexed.
How should out-of-stock and discontinued products be handled?
Separate the temporary from the permanent, because the site almost always treats them the same and they route differently. A temporary stockout stays live at 200 with OutOfStock or BackOrder in the schema — the page holds its equity and its ranking through the gap. A permanently discontinued product is a routing decision based on what it earned: one carrying links and ranking history gets its equity routed to the nearest replacement or parent category; one carrying nothing gets a clean 410. Redirecting every dead product to the homepage is the common default and the wrong one — Google reads it as a soft 404 and drops the equity anyway.
What tools does an ecommerce SEO audit actually need?
Fewer than the tool lists suggest. Search Console and a log file cover most of the index and crawl work; a crawler that renders JavaScript (Screaming Frog or similar) for the internal-link graph and the custom extractions; the URL Inspection and CrUX APIs for index status and Core Web Vitals by template; the platform’s own exports for inventory and on-site search. Ahrefs or Semrush help on the demand side, but they’re one input, not the audit — the sharper demand signals come from Search Console queries, zero-result on-site searches and marketplace autocomplete.
Why is the product feed part of an SEO audit now?
Because AI shopping surfaces and Google’s own shopping panels pull product data from the feed, not the page. ChatGPT’s carousel, Perplexity’s product answers, AI Overviews and Merchant Center all read the same structured file, so a feed that’s drifted from the site — wrong price, wrong availability, missing identifiers — drops the product from consideration and gets it disapproved, no matter how clean the page is. The feed is a second index with its own failures, and until recently no audit looked at it.
Within a year I’d expect feed-to-site sync to be a standard line on every serious audit, sitting next to canonical hygiene, because it stopped being a paid-media concern and became a ranking surface. The stores that read their own systems this way — index decisions, crawl allocation, the feed as its own index — are the ones still visible when the next surface opens. If you’d rather I run this on your store than run it yourself, that’s our ecommerce SEO audit.