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.

Last updated 2026-04-30 · Ranking-spread signal · 8 pts

8 / 70Ranking-spread weight
5+URL shapes per Shopify PDP
1 lineOf theme.liquid to fix
What this signal scores: presence and shape of <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>:

Bad — 0 pts

Missing entirely

<head>
  <title>Wool Runner</title>
  <!-- no canonical -->
</head>
Partial — 4 pts

Relative URL

<link rel="canonical"
  href="/products/wool-runner">
Partial — 4 pts

Wrong host (myshopify.com)

<link rel="canonical"
  href="https://my-store.myshopify.com/products/wool-runner">
Correct — 8 pts

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

How to test it on your store

  1. Open any PDP in a browser. View source.
  2. Search for rel="canonical". If missing — 0 credit.
  3. If present, check the href:
    • Starts with https:// + your custom domain → 8 pts.
    • Starts with / (relative) → 4 pts.
    • Points at .myshopify.com → 4 pts.
  4. 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.
  5. Stress-test: append ?utm_source=test to 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

Default Shopify: verify, don't fix2 minfree

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.

Custom theme dropped the tag3 minfree

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.

Hydrogen / Remix on Vercel or Oxygen15 minfree

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.

Next.js + Storefront API15 minfree

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

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