Technical SEO foundations for Shopify stores
The platform fixes your URL structure, duplicates products across collections, and leaves canonicals and structured data to whatever your theme happens to do.
GuideJune 3, 20268 min read
Most technical SEO advice is written for a CMS where you control the routing table. Shopify is not that. The platform fixes the shape of every URL, generates more of them than you asked for, and leaves the parts that actually matter — canonicals, structured data, hreflang — to whatever your theme happens to do. Knowing which of those you can change and which you have to work around is most of the job.
The URL structure is the platform's, not yours
Every storefront resource sits under a fixed prefix. Products live at /products/<handle>, collections at /collections/<handle>, static pages at /pages/<handle>, articles at /blogs/<blog>/<article>. You can edit every handle. You cannot remove a prefix, nest a product under a category path, or invent a new top-level route. A migration plan that assumes /mens/shoes/runner-01 is a plan that will not survive contact with the platform.
Accept that early and the rest gets simpler. The handle is the lever you have, and it is a good one: it is the only human-readable part of the URL, it stays stable if you leave it alone, and changing it after launch costs you a redirect and some of the signal attached to the old address. Set handles deliberately at migration time, while nothing is indexed yet, then stop touching them.
The one place the platform actively works against you is the product URL under a collection. A product reachable through a collection also answers at /collections/<collection>/products/<handle>. A product sitting in twelve collections therefore has thirteen working addresses. The canonical tag in a competent theme points all of them at /products/<handle>, which is the correct behaviour and the first thing to verify on any theme you did not write. It is a one-line check and it is wrong often enough to be worth doing on day one.
Scroll the figure sideways to see all of it
Collections are where the duplication comes from
Tag paths
Shopify serves tag-filtered collections at their own addresses: /collections/shoes/waterproof, and combinations at /collections/shoes/waterproof+size-42. These are real, crawlable pages. With eight tags on a collection you have generated a combinatorial surface nobody linked to on purpose and nobody wants indexed.
The rule worth applying: a tag path earns indexation only if a person would search for it and the page holds enough products to be worth landing on. "Waterproof running shoes" might. "Waterproof + size 42 + blue" does not. Everything below that line gets a canonical back to the parent collection or a noindex, and either is better than leaving it open.
Filters and the robots file
Storefront filtering adds query parameters. Parameters are a milder problem than tag paths because most themes canonical them away by default, but "most themes" is not "your theme". Check what a filtered collection emits as its canonical. If it self-canonicals to the filtered URL, you have handed a crawler an unbounded space to explore, and it will.
Shopify lets you edit robots.txt.liquid, which is the blunt instrument for this. It is useful for the parameter space and for internal search result pages, and it is the wrong tool for anything you also want deindexed — a blocked URL cannot be crawled, so a crawler never reads the noindex you put on it. Block what should never be fetched; noindex what should be fetched and not listed. Confusing the two is how a page stays in an index for months with no description under it.
Thin collections
Automated collections are cheap to create, which is why stores end up with a hundred of them and a third holding two products each. A collection with two products is a page with no reason to exist in an index: it competes with the products themselves, it holds nothing a person searched for, and in aggregate these pages tell a crawler the site is mostly empty shelves.
Set a floor — a product count below which a collection is not indexed — and enforce it in the template rather than by memory. The alternative is not "a few thin pages". It is a slow accumulation nobody audits until someone wonders why the collection pages stopped ranking.
Canonicals and pagination are the same problem
Themes expose Shopify's canonical_url and most of them output it, so the question is never whether you have a canonical but whether it says what you meant. Three cases account for nearly all the mistakes.
The first is the collection-scoped product URL above. The second is a filtered or sorted collection self-canonicalling. The third is pagination, and it is the one people get wrong in the opposite direction: page two of a collection should canonical to page two, not to page one. Pointing every page at the first one tells a crawler that pages two through nine do not exist, which is a fast way to lose the products that only appear deep in a collection.
Rel prev/next is no longer a signal worth engineering around. What matters is that each paginated page is self-canonical, that products deep in a collection are reachable in a small number of clicks, and that the paginate size is not so small a nine-hundred-product collection becomes thirty-eight pages of crawl for no reason.
Shopify publishes /sitemap.xml from what is on the online store channel, which makes it an audit rather than a lever: if it lists collections you never meant to expose, the fix is to unpublish them.
Scroll the figure sideways to see all of it
Structured data belongs to the theme, and to one place in it
Shopify does not emit product structured data for you. Themes do it, apps do it, and review apps do it, which is how a product page ends up carrying three Product blocks that disagree about price. Pick one source — a snippet in the theme — and delete the rest.
What that snippet should say is less interesting than what it should not. Emit Product with name, description, image, sku, brand and an Offer carrying price, priceCurrency and an availability that reflects the real variant state. Emit BreadcrumbList if your breadcrumbs are real. Do not emit aggregateRating unless the reviews exist and are visible on the page; it is the most common self-inflicted reason a store loses rich results.
Variants are the part that needs thought. A product whose variants sit at different prices needs an AggregateOffer with lowPrice and highPrice — a plain Offer has no way to say “from”, and availability has to stay honest when the selected variant is out of stock. A snippet that hard-codes the first variant's price is correct on the default view and wrong on every other one. This is ordinary theme work, and it is where structured data actually gets fixed rather than layered.
That same snippet is doing double duty now. A generative answer engine reading your product page parses the same markup, and a page whose structured data disagrees with its visible content is a page that gets summarized wrong. We treat that as one problem rather than two — see AI search readiness for where the two overlap.
hreflang when you run Markets
Markets gives you multiple locales, currencies, and either subfolders or separate domains. On the SEO side the thing that matters is the alternates block: each localized URL has to declare every other localized version of the same page, including itself, plus an x-default. Shopify's hreflang_tags object generates this, and a theme that omits the call generates nothing at all.
Two failure modes are worth naming. The first is a mismatch between what the alternates declare and what the URL actually serves — a tag claiming es-mx for a page that renders in English is worse than no tag. The second is redirecting by IP: a crawler arriving from one country and being bounced to that country's storefront never sees the other markets. Serve what was requested, and offer a switch rather than a redirect.
A market that sells the same language to a different country still wants an alternate — en-CA alongside en-US is how you tell a search engine which storefront serves which country, and it is the documented mechanism for region targeting. What does not deserve its own address is a market that changes nothing at all. Our ecommerce SEO implementation work usually starts by drawing what the market map actually is, before anything is emitted.
The order to do this in
Canonicals first, because they are cheap and they decide which of your many addresses count. Then the tag and filter surface, because it is the largest source of pages you did not intend. Then structured data, consolidated into one snippet. Then thin collections. Then hreflang, which only matters once the rest is coherent — declaring alternates between two sets of duplicated pages just doubles the mess.
None of this is a growth tactic and none of it shows up as a spike. It is the layer that decides whether the rest of your work is legible to a crawler at all, and it stays fixed once it is fixed, which is more than most SEO work can claim.