Fastest fix: open Google Search Console, run URL Inspection on the page that won’t rank, and compare the User-declared canonical with the Google-selected canonical. If they differ, your canonical tag is pointing somewhere Google won’t follow. For ~90% of pages the correct value is the page’s own URL (self-canonical) — set that, redeploy, and request a re-crawl.
<link rel="canonical"> tells Google “if this content exists at several URLs, assign indexing and ranking signals to this one.” But Google’s own docs are blunt about it: “indicating a canonical preference is a hint, not a rule” — Google weighs your tag against redirects, HTTP vs HTTPS, sitemap presence, and internal links, then picks the canonical it trusts most. Get the tag wrong and you hit one of two outcomes:
- The page you actually want indexed never enters the index, or
- The URL that ranks is the wrong version — a param-laden URL, a staging subdomain, an alternate locale, even someone else’s site.
The trap: in the browser everything looks fine. Only Google knows you’re shooting yourself in the foot. The four patterns below are all real.
Which bucket are you in?
| Symptom in Search Console | Likely cause | Jump to |
|---|---|---|
| Page Indexing report: “Alternate page with proper canonical tag” on a page you DID want indexed | Canonical points away from the page (often noindex or a dead target) | Cause 1 |
| URL Inspection: Google-selected canonical is a different domain or host | Cross-domain / www-vs-apex canonical | Cause 2 |
| Wrong-language page ranks; locale group collapsed to one URL | Canonical fights hreflang | Cause 3 |
| ”Duplicate, Google chose different canonical” — selected URL differs only by slash/case/protocol | Normalization mismatch | Cause 4 |
Common causes
1. Canonical points to a non-existent / 404 / noindexed URL
Typical patterns:
<!-- Template hardcoded a path, but the target URL was later deleted -->
<link rel="canonical" href="https://yourdomain.com/legacy/post" />
<!-- Domain changed but the template didn't get updated -->
<link rel="canonical" href="https://staging.yourdomain.com/article" />
<!-- Self-canonical, but page is noindex -->
<meta name="robots" content="noindex" />
<link rel="canonical" href="https://yourdomain.com/this-very-page" />
Google reads the canonical, fetches that URL, gets a 404 or noindex, and the whole group (original plus canonical target) drops from the index. The noindex + self-canonical combination is especially nasty: you’re telling Google “index this exact page” and “never index this page” in the same response. Google honors the noindex.
How to confirm: Search Console -> URL Inspection -> expand Page indexing and read the Google-selected canonical field (sitting next to User-declared canonical). The three possible values are “Inspected URL” (Google picked the page itself), “Same as user-declared canonical” (Google accepted your tag), or a different URL (Google overrode you). One gotcha as of June 2026: the canonical fields only populate from the indexed data, not from Test Live URL — a live test cannot tell you which canonical Google will choose. If it shows a different URL, fetch that URL and check the status:
curl -sI "https://yourdomain.com/legacy/post" | head -1
# Want: HTTP/2 200 (not 404, 301-chain, or a noindex page)
2. Cross-domain canonical without intent or reciprocal mention
A cross-site canonical (pointing to another domain or CDN host) only does what you want when you actually want signals to transfer to that other URL — for example, self-syndicated content. Common screwups:
- You copied another site’s HTML template and forgot to update the canonical (it still points to the original site)
- You serve through a CDN subdomain (
cdn.example.com) but didn’t canonical back to the main host - Both
www.and the apex domain resolve, and the canonical points to a mix of the two
<!-- Your page lives on www.yourdomain.com -->
<!-- Wrong: splits its own authority to the apex -->
<link rel="canonical" href="https://yourdomain.com/article" />
Note Google’s own caveat (from its canonicalization docs): the canonical link element is not recommended for syndication partners, “because the pages are often very different” — Google instead tells syndication partners to block indexing of the copy. Use a cross-domain canonical only when the duplicate is genuinely the same content under your control.
How to confirm: Search Console -> Page Indexing report -> open “Duplicate, Google chose different canonical.” That status is exactly these cases — Google found another host more authoritative and consolidated to it.
3. Canonical fights hreflang / robots / sitemap
hreflang requires each locale version to reference all the others, but the canonical must point at its OWN locale. Otherwise the two signals cancel:
<!-- on /zh/article -->
<link rel="canonical" href="https://yourdomain.com/zh/article/" />
<link rel="alternate" hreflang="zh" href="https://yourdomain.com/zh/article/" />
<link rel="alternate" hreflang="en" href="https://yourdomain.com/en/article/" />
<link rel="alternate" hreflang="x-default" href="https://yourdomain.com/en/article/" />
If the Chinese page’s canonical wrongly points to the English page, the entire Chinese locale group can drop from the index — only the English page ranks, and it ranks badly for Chinese queries. Each translated URL must self-canonical and only cross-reference its siblings via hreflang.
Another conflict: your sitemap lists URL A, but page A’s canonical points to URL B. Google treats rel="canonical" as a stronger signal than sitemap presence, so it follows the canonical and the sitemap entry is effectively wasted.
4. Canonical mismatches by case / trailing slash / protocol
Google treats these as different URLs:
HTTPS://yourdomain.com/Articlevshttps://yourdomain.com/articlehttps://yourdomain.com/articlevshttps://yourdomain.com/article/https://yourdomain.com/articlevshttp://yourdomain.com/article
If the sitemap, internal links, and canonical disagree on case, slash, or protocol, you’re feeding Google contradictory signals. Google generally prefers the HTTPS version and can resolve a single clean redirect hop, but a chain (e.g. http -> https -> trailing-slash variant) weakens the signal enough that Google may pick a version you didn’t intend as the canonical.
Shortest path to fix
Step 1: Audit canonicals across the site
This script walks your sitemap and extracts the canonical for each URL, flagging the four failure modes above:
// scripts/audit-canonicals.mjs
import { XMLParser } from "fast-xml-parser";
const sitemapUrl = "https://yourdomain.com/sitemap.xml";
const expectedHost = "yourdomain.com";
const xml = await fetch(sitemapUrl).then((r) => r.text());
const { urlset } = new XMLParser().parse(xml);
const urls = urlset.url.map((u) => u.loc);
for (const url of urls) {
const html = await fetch(url).then((r) => r.text());
const m = html.match(/<link\s+rel=["']canonical["']\s+href=["']([^"']+)["']/i);
const canonical = m?.[1] ?? "(missing)";
const issues = [];
if (canonical === "(missing)") issues.push("MISSING");
else {
const c = new URL(canonical);
if (c.host !== expectedHost) issues.push(`CROSS-HOST: ${c.host}`);
if (c.protocol !== "https:") issues.push("NON-HTTPS");
if (c.pathname !== new URL(url).pathname) issues.push("PATH-DIFFERS");
}
console.log(`${url}\t-> ${canonical}\t${issues.join(",")}`);
}
Run node scripts/audit-canonicals.mjs > canonicals.tsv and skim the flagged lines.
Step 2: Default to self-canonical, only point elsewhere when necessary
Roughly 90% of pages should canonical to themselves. In your layout:
---
const canonical = Astro.url.href;
---
<link rel="canonical" href={canonical} />
Only point elsewhere in these specific cases:
| Case | Canonical points to |
|---|---|
Pagination /blog?page=2 | /blog |
Param variants /p?utm=x | /p |
Mobile subdomain m.example.com/p | example.com/p |
| Self-syndicated (you wrote it, also published it elsewhere) | Your master version |
| Re-syndicated (someone else’s original) | Their master version |
Step 3: When canonical + hreflang coexist, generate both from one helper
export function buildHreflangAndCanonical(currentLang, slug, langs) {
const base = "https://yourdomain.com";
const canonical = `${base}/${currentLang}/${slug}/`;
const alternates = langs.map((l) => ({
hreflang: l,
href: `${base}/${l}/${slug}/`,
}));
alternates.push({ hreflang: "x-default", href: `${base}/en/${slug}/` });
return { canonical, alternates };
}
Every page goes through this function. The canonical is always the current locale; hreflang covers all locales plus x-default; the loop always closes.
Step 4: Add a build-time check
In prebuild:
// scripts/check-canonical-build.mjs
import fg from "fast-glob";
import fs from "node:fs";
const files = fg.sync("dist/**/*.html");
const issues = [];
for (const f of files) {
const html = fs.readFileSync(f, "utf8");
const cm = html.match(/<link\s+rel=["']canonical["']\s+href=["']([^"']+)["']/i);
const robots = html.match(/<meta\s+name=["']robots["']\s+content=["']([^"']+)["']/i);
if (!cm) issues.push(`${f}: MISSING canonical`);
if (robots?.[1]?.includes("noindex") && cm) {
issues.push(`${f}: noindex + canonical (canonical wasted)`);
}
}
if (issues.length) {
console.error(issues.join("\n"));
process.exit(1);
}
This catches “the template wrote the wrong canonical” before deploy.
Step 5: Force a re-crawl on key URLs
In Search Console, run URL Inspection -> Request Indexing on your 5-10 most important URLs. As of June 2026 the URL Inspection tool caps you at roughly 10-12 manual requests per property per day (Google does not publish the exact number); once you hit it, the Request Indexing button greys out for about 24 hours, and the cap is per verified property, so switching accounts or browsers won’t reset it. Spend the requests on your highest-value pages. Re-evaluation is not instant: expect a few days to about two weeks for Google to re-crawl and re-process the corrected canonical.
How to confirm it’s fixed
You haven’t fixed anything until Google agrees. After redeploy:
- URL Inspection -> Test Live URL -> confirm the live page now shows the correct User-declared canonical (the page’s own URL for self-canonical pages).
- Wait for the re-crawl, then inspect the indexed version: the Google-selected canonical should read “Inspected URL” or “Same as user-declared canonical.”
- In the Page Indexing report, the page should move out of “Alternate page with proper canonical tag” / “Duplicate, Google chose different canonical” and into Indexed.
- Spot-check with
curl:
curl -s "https://yourdomain.com/article/" | grep -i 'rel="canonical"'
# Should print exactly one canonical, pointing at this same URL.
FAQ
Why is Google ignoring my canonical tag? Because a canonical is a hint, not a directive. If your other signals contradict it — internal links, the sitemap, redirects, or an HTTP/HTTPS mismatch — Google’s heuristics can override the tag and choose a different canonical. Align every signal on one URL and the override usually goes away.
Is “Alternate page with proper canonical tag” an error? Not by itself. It’s the correct status for pages you intentionally canonical away (param URLs, paginated pages). It’s only a problem when it appears on a page you actually wanted indexed — that means the canonical is pointing somewhere it shouldn’t.
Can I have both a canonical and a noindex on the same page?
No — they contradict each other, and Google honors the noindex. Pick one: either index the page (self-canonical, no noindex) or remove it from the index (noindex, drop the canonical pointing to itself).
Should every page self-canonical? About 90% should. Point elsewhere only for true duplicates you control: pagination, tracking-param variants, mobile subdomains, and self-syndicated copies. When in doubt, self-canonical.
How long until the fix shows up in search? After you Request Indexing, plan on a few days to roughly two weeks for Google to re-crawl and re-process. The change is “done” only when the indexed version’s Google-selected canonical reflects it — not the moment you deploy.
Does a canonical pass full ranking value like a 301?
Close, but a 301 redirect is the stronger consolidation signal. Use a 301 when the old URL should genuinely go away; use rel="canonical" when both URLs must stay reachable (e.g. printable or param versions).
Prevention
- All canonicals go through one
buildCanonical()helper — no hand-written tags. - Run
audit-canonicals.mjssite-wide before shipping a new template. - CI prebuild blocks: missing canonical,
noindex+ canonical coexisting, cross-host canonicals. - Canonical / sitemap / internal links agree exactly on case and trailing slash — generate every URL through a single
urlFor()function.
Related
- Canonical wrong after domain change
- Alternate page with proper canonical tag
- Duplicate, Google chose different canonical
- Googlebot Crawl Spikes But Impressions Stay Flat
- Infinite Scroll Pages Don’t Get Indexed
- Page Not Mobile-Friendly Warning
External references: Google: URL canonicalization, Google: fix canonicalization issues
Tags: #SEO #Google #Search Console #Indexing