Home · The 15 signals · Canonical URL
Shopify canonical URL on PDPs
A canonical URL tells AI shopping agents and search crawlers "this is the one true URL for this product — consolidate every signal you've collected against it." Shopify makes this critical because every PDP is reachable via at least five URL shapes by default: the root path, every collection-scoped path, the .myshopify.com subdomain, marketing-decorated tracking URLs, and any URL emitted by your apps. Without a canonical, AI agents split authority across all five — your "official Allbirds Wool Runner" page accumulates fractional credit on each shape instead of full credit on one. Fix is one Liquid line in layout/theme.liquid; the failure mode is the kind of slow-leak signal degradation that's hard to attribute when ranking spread softens.
<link rel="canonical"> on a sampled PDP. Absolute URL on the root domain (https://store.com/products/foo) = 8 pts. Relative URL (/products/foo) = 4 pts. Pointing at .myshopify.com instead of the custom domain = 4 pts. Missing tag entirely = 0 pts.
What it is — and why Shopify makes this hard
One Shopify product is reachable from many URLs because of how Shopify routes collection-scoped product paths. The same Wool Runner product can appear at any of these URLs, returning the same page body each time:
- https://store.com/products/wool-runner
- https://store.com/collections/all/products/wool-runner
- https://store.com/collections/mens/products/wool-runner
- https://store.com/collections/sale/products/wool-runner
- https://store.com/products/wool-runner?variant=12345
- https://store.com/products/wool-runner?utm_source=email&utm_campaign=launch
- https://www.store.com/products/wool-runner
- https://my-store.myshopify.com/products/wool-runner
Without a canonical, AI agents and search engines treat each URL as a separate "potential product page." Ranking signals — page authority from inbound links, scan history, citation count, dwell time — get split across all of them. Worse, when the agent eventually decides to cite a URL, it picks one effectively at random — usually whichever one it crawled first. Your customer sees a citation pointing at /collections/sale/products/... permanently after the sale ended.
The canonical solves this in one HTML tag in the <head>:
Missing entirely
<head> <title>Wool Runner</title> <!-- no canonical --> </head>
Relative URL
<link rel="canonical" href="/products/wool-runner">
Wrong host (myshopify.com)
<link rel="canonical" href="https://my-store.myshopify.com/products/wool-runner">
Absolute URL on the custom domain
<link rel="canonical" href="https://store.com/products/wool-runner">
Default Shopify themes (Dawn, Trade, Sense, Refresh) emit the absolute, custom-domain canonical correctly out of the box via the Liquid global {% raw %}{{ canonical_url }}{% endraw %}, which Shopify auto-rewrites to the absolute form. Custom-built themes and headless front-ends are where this signal usually breaks.
Why AI shopping agents care
- URL consolidation. Every signal an AI agent collects about a URL — number of inbound links, scan freshness, click-through rate from prior citations — accumulates against the URL the page declares as canonical. Without it, signals split across the 5+ URL shapes; the canonical gathers them. Same authority pool, one anchor instead of five.
- Citation correctness. When an AI agent surfaces your product in a response, it cites the canonical URL. Without one, agents pick the URL they happened to crawl first. Result: your customer lands on
/collections/sale-2024/products/...three months after the sale, and the page either redirects awkwardly or shows a stale collection breadcrumb that breaks the customer's trust. - Tracking-parameter neutralization. Marketing-decorated URLs with
utm_*,fbclid,gclid, andref=parameters are everywhere — every email, every paid campaign, every affiliate link. Without a canonical, each tracking-decorated URL becomes a separate "page" in the agent's index, exploding into dozens of duplicates. Canonical de-decorates them all. - Variant-URL deduplication. Shopify products with variants emit URLs like
/products/wool-runner?variant=12345when the user picks a variant. Same page body; different URL. Without a canonical, every variant ID becomes a separate page. With a canonical pointing back at/products/wool-runner, agents understand variants as one product with options.
How to test it on your store
- Open any PDP in a browser. View source.
- Search for
rel="canonical". If missing — 0 credit. - If present, check the
href:- Starts with
https://+ your custom domain → 8 pts. - Starts with
/(relative) → 4 pts. - Points at
.myshopify.com→ 4 pts.
- Starts with
- Stress-test: open a collection-scoped URL like
https://yourstore.com/collections/all/products/something. View source. The canonical should point at the root form (/products/something) — not the collection-scoped form. If the canonical reflects the collection path, it's not actually canonicalizing. - Stress-test: append
?utm_source=testto a PDP URL. Reload. View source. Canonical should still be the un-decorated URL.
The free CatalogScan scan checks all three subforms — root, collection-scoped, tracking-decorated — and reports which shape your canonical resolves to.
How to fix it
If you're on Dawn, Trade, Sense, or any current Shopify Online Store 2.0 theme, the canonical is already correct. Themes use Liquid's global {% raw %}{{ canonical_url }}{% endraw %} in layout/theme.liquid:
<link rel="canonical" href="{% raw %}{{ canonical_url }}{% endraw %}">
Shopify auto-emits the absolute custom-domain URL pointing at the root product path (not the collection-scoped path) and strips tracking parameters. View-source on any PDP to verify; if it's there and absolute, you're done.
If a theme rewrite or app removed the canonical, restore it: open layout/theme.liquid, find the <head> block, add:
<link rel="canonical" href="{% raw %}{{ canonical_url }}{% endraw %}">
One commit, every PDP fixed. The Liquid global handles all the canonicalization rules — root over collection-scoped, custom domain over .myshopify.com, no tracking params.
In your PDP route's loader, derive the canonical from the request URL: take the pathname (which Hydrogen normalizes to the canonical form via the routes API) and prepend your custom domain. In app/routes/products.$handle.tsx, in the links export, return [{`{ rel: 'canonical', href: \`https://yourstore.com/products/${handle}\``}}]. Strip query parameters before constructing the URL — agents rely on the canonical being parameter-free.
App Router: in app/products/[handle]/page.tsx, export generateMetadata returning {`{ alternates: { canonical: \`https://yourstore.com/products/${handle}\` } }`}. Next.js renders this as <link rel="canonical"> on the page. For Pages Router, set the same tag manually in the <Head> component.
5 mistakes we keep finding
1. Canonical reflects the current URL, not the canonical URL
Custom themes sometimes use {% raw %}{{ request.path | prepend: shop.url }}{% endraw %} instead of {% raw %}{{ canonical_url }}{% endraw %}. Result: visiting /collections/all/products/foo emits a canonical pointing at /collections/all/products/foo instead of /products/foo. Same page, two canonicals depending on which URL the agent fetched. Use the global, not the request path.
2. Canonical includes tracking parameters
Some headless rebuilds emit canonical = window.location.href via JavaScript or build it from the full request URL on the server. UTM/fbclid/gclid parameters end up in the canonical. Every campaign creates a new "canonical" URL, multiplying your duplicate problem instead of solving it. Strip query strings before building the canonical, except for variant_id if you want variant-level canonicalization (rare).
3. Canonical points at the wrong domain after migration
Stores that migrated from .myshopify.com to a custom domain sometimes leave the canonical pointing at the old .myshopify.com URL. Agents follow the canonical, end up on the wrong host, get a redirect to the custom domain, treat the original as the authority. You're effectively delegating authority to a URL you don't want anyone to land on. Verify the canonical host matches your live host, especially after a domain change.
4. Variant URLs canonicalize to themselves
Some apps emit a canonical including the ?variant=12345 parameter. Result: every variant becomes its own canonical URL, fragmenting authority across however many variants you have. The product canonical should always point at the variant-less root, not the variant-decorated path. Variant pages are the same product with options, not separate products.
5. Multiple canonical tags on the same page
An SEO app injects a canonical, the theme also emits one, both end up in <head>. Different parsers pick different tags — Google takes the first, some agents take the last, others throw an error and ignore both. View-source and confirm you have exactly one <link rel="canonical"> tag. If two, disable one source.
See also
- The 15 signals — full reference
- Sitemap.xml: should list canonical URLs only
- Product JSON-LD: the URL field should match your canonical
- Hreflang on PDPs: pairs with canonical for multi-locale stores
- Full 18-signal Agentic Storefronts checklist
- Leaderboard: 100 DTC stores scored on canonical and 14 other signals
Where does your canonical resolve?
Free 2-minute scan. We test the three URL-shape variations against your PDPs and report which form your canonical points at, alongside 14 other AI-shopping signals.
Scan my store → See all 15 signals