Skip to content

Canonical URLs

A canonical URL is the version of a page you want search engines to treat as the real one when several URLs serve the same content. You declare it with a link rel=canonical in the head, and on a normal page it should point at that page's own clean URL.

What we measured

We ran a canonical audit across all 215 of our own exported pages. 204 carry a correct self-referencing canonical, 10 carry none, and 1 points at a different URL. Checked individually, all 11 exceptions are correct: every one is a noindex page, so it is excluded from search on purpose and a canonical would decide nothing. A tool reporting 11 canonical issues on this site would be reporting 11 non-problems.

Why it matters

  • When the same content is reachable at several URLs, search engines have to pick one. The canonical is how you make that choice instead of letting it be made for you.
  • Ranking signals earned by three URL variants consolidate onto one when the canonical agrees. Left to chance they can stay split.
  • The classic failure is silent and severe: a template that canonicalizes every page to the homepage tells search engines your whole site is one page.
  • It is a hint, not a directive. Redirects, internal links and sitemap URLs all vote too, and a canonical that contradicts them tends to lose.

The failure

The failure: every page canonicalizing to the homepage
<!-- on /services/technical-seo -->
<link rel="canonical" href="https://www.axiondeepdigital.com/" />

<!-- on /services/web-design -->
<link rel="canonical" href="https://www.axiondeepdigital.com/" />

<!-- on /blog/some-article -->
<link rel="canonical" href="https://www.axiondeepdigital.com/" />

One hardcoded value in a shared layout produces this on every page at once. Each page is telling search engines it is really the homepage, so none of them are candidates to rank for anything. It is the same shape of bug as a schema generator error: one wrong line, reproduced everywhere.

The fix

The fix: derive it from the page's own path
export async function generateMetadata({ params }) {
  const { slug } = await params;
  return {
    alternates: { canonical: `/learn/${slug}` },
  };
}

Generated from the route rather than typed per page, which is how our 204 correct canonicals stay correct without anyone maintaining them. A canonical you have to remember to set is one you will eventually forget.

Verify the fix

Changing the code is not the same as fixing the problem. Confirm it.

  1. Scan the rendered output rather than your templates. This script reports every page whose canonical is missing or does not point at itself.
  2. For each exception, check the robots directive before calling it a defect. A noindex page does not need a canonical, and counting it as a failure is a false alarm.
  3. Confirm the canonical URL returns 200 and is not redirected, blocked by robots.txt, or itself noindex.
  4. Check that the canonical, your internal links and your sitemap all name the same URL. Disagreement is how a correct canonical still loses.
  5. Use Search Console's URL Inspection on important pages to see the canonical Google actually selected, which can differ from the one you declared.
Run this against your own rendered pages
import re, glob

BASE = "https://www.example.com"

for f in sorted(glob.glob("out/**/*.html", recursive=True)):
    html = open(f, encoding="utf-8", errors="ignore").read()
    if "<html" not in html:
        continue
    path = "/" + f[len("out/"):].replace(".html", "")
    if path.endswith("/index"):
        path = path[:-6] or "/"

    canonical = re.search(r'link rel="canonical" href="([^"]+)"', html)
    noindex = 'content="noindex' in html

    if not canonical:
        print("NO CANONICAL", path, "(noindex)" if noindex else "(INDEXABLE)")
    elif canonical.group(1).rstrip("/") != (BASE + path).rstrip("/"):
        print("POINTS ELSEWHERE", path, "->", canonical.group(1),
              "(noindex)" if noindex else "(INDEXABLE)")

Point it at your build output. The noindex flag on each line is the part that matters: it separates the findings you must act on from the ones that are correct as they are. Ours printed 11 lines and every one ended in (noindex).

Exceptions and misconceptions

A noindex page does not need one

If the page is excluded from search, the canonical decides nothing. This is why 11 of our 215 pages report as exceptions and none of them are defects. Check indexability before counting.

Paginated pages are not duplicates

Page 2 of a listing should usually canonicalize to itself, not to page 1. Pointing every page of a series at the first one tells search engines the rest do not exist.

Cross-domain canonicals are legitimate

Syndicating an article to another site and having that copy canonicalize back to your original is the intended use. A tool flagging the external target as wrong is flagging the thing working correctly.

It is a hint, and Google can overrule it

If your redirects, internal links and sitemap all point somewhere else, Google may select a different canonical than the one you declared. When that happens the fix is usually the contradicting signal, not the tag.

Watch it

Primary sources

The measurement above comes from our study, State of Small Business Websites 2026.

Related lessons

Check your own site for this

DeepAudit AI renders your page in a real browser and reports the affected code, so you can see exactly where each finding came from. Free, no signup.

Run a free audit

Last reviewed 2026-09-17. Checks covered: Canonical URL.