Technical SEO
Canonical Tags: What They Are, How to Implement Them, and 9 Mistakes
A canonical tag is a <link rel="canonical"> element in a page’s <head> that names the URL you want search engines to treat as the main version of that content. When the same page is reachable at several URLs, the canonical tag tells Google which one to index and show, and where to consolidate the signals from the others.
This guide covers the syntax, self-referencing and cross-domain canonicals, hreflang, the HTTP header version for files, and how Next.js, Astro and WordPress set the tag (with output from a small Astro build run for this article). It ends with nine mistakes and how to check what Google chose.
Key takeaways
- A canonical tag is a hint, not a rule. Google says so directly and may pick a different URL if your other signals disagree.
- Put exactly one canonical in the
<head>, use an absolute URL, and add a self-referencing canonical to the preferred page itself. - For non-HTML files such as PDFs, send the canonical as a
LinkHTTP header instead of a tag. - Frameworks generate the tag from config. Most canonical bugs are config bugs: a missing site URL, a canonical inherited from a root layout, or two plugins both printing one.
- A canonical and hreflang must agree: each language version canonicalises to itself, never to another language.
What a canonical tag is
Canonicalisation is Google’s process of choosing one representative URL from a set of duplicate or near-duplicate pages. In Google’s words, the canonical URL is “the URL of a page that Google chose as the most representative from a set of duplicate pages.” The canonical tag is how you state which URL you would prefer it to choose.
The element itself is defined in RFC 6596, and Google supports it as described there. It looks like this:
<link rel="canonical" href="https://example.com/dresses/green-dress/" />
Choosing the canonical matters because of what Google does next. The canonical page is crawled most regularly and duplicates less often, and Google uses the canonical page as the main source to evaluate content and quality. Search results usually point to it, unless a duplicate suits the searcher better (a mobile URL for a mobile user, for example).
Why it is a hint and not a directive
Google treats a declared canonical as one signal among several. It lists them in order of strength: redirects are a strong signal, rel="canonical" annotations are a strong signal, and sitemap inclusion is a weak one. The methods stack, so using two that agree raises the chance your preferred URL is chosen.
When you need one
Google is candid that none of these methods is required: if you declare nothing, it picks the version it judges best. You declare a canonical when you care which URL wins. Google’s documentation lists the usual sources of duplication:
- Parameters and tracking codes:
?gclid=,?utm_source=, session IDs. - Sorting and filtering: the same category list at
?sort=priceand?colour=blue. - Protocol and host variants: HTTP and HTTPS,
wwwand non-www. - Device variants: a separate mobile URL for the same page.
- Accidental copies: a demo or staging version left open to crawlers.
Duplicate content of this kind is normal and not a spam problem. The cost is diluted signals, split reporting and crawl time spent on copies.
Canonical tag syntax
The rules for the HTML element are short, and each one comes from Google’s canonical documentation:
- It goes in the
<head>. The element is only accepted there, so the<head>has to be valid HTML up to that point. - Use an absolute URL. Relative paths are supported but not recommended, because they can resolve against the wrong host (a crawlable staging site, for example).
- One per page. Do not send one URL in the tag and another in a header or sitemap.
- No fragments. A URL with
#sectionis not a valid canonical, since Google generally ignores fragments. - Link internally to the canonical. Internal links pointing at the preferred URL reinforce the tag.
<head>
<meta charset="utf-8" />
<title>Green dress | Example</title>
<link rel="canonical" href="https://example.com/dresses/green-dress/" />
</head>
Put it early in the <head>. Google’s own post on common rel=canonical mistakes recommends including it “as early as possible” to avoid parsing problems.
One syntax trap is worth knowing before you add hreflang or mobile annotations. Google ignores rel="canonical" elements that carry hreflang, lang, media or type attributes. Those canonicals are not used for canonicalisation. Alternates always get their own <link rel="alternate"> element.
Self-referencing canonicals
A self-referencing canonical is a canonical tag on the preferred page that points at that page’s own URL. Google recommends it explicitly: “Do include a rel="canonical" link on the canonical page itself.”
The practical reason is that you do not control every URL that reaches your page. An ad click appends ?gclid=, a newsletter appends UTM parameters, and a scraper links to an odd variant. If the page always prints its own clean URL as the canonical, every one of those variants points back to the right place without you listing them.
<!-- Served at https://example.com/pricing/ and at /pricing/?utm_source=newsletter -->
<link rel="canonical" href="https://example.com/pricing/" />
The key detail is that the canonical is built from the clean path, not from the full requested URL. A template that echoes the request URL, query string included, gives each parameter variant a self-referencing canonical of its own, which is the opposite of what you want.
Cross-domain canonicals
A canonical can point to a URL on a different domain. Google announced support for cross-domain rel="canonical" in December 2009, for cases such as moving a site from a server that cannot do redirects.
Three rules from that announcement still apply:
- Map page to page. Point each old URL at its equivalent, not every URL at the new home page. Google says a home page catch-all “could result in problems.”
- Prefer a redirect where you can. A 301 is the clearer instruction for a move. Use a cross-domain canonical when a redirect is impossible, and see SEO redirects for the redirect side.
- Keep both pages crawlable. A canonical on a URL blocked in robots.txt is never read.
Syndication is where the advice has changed. Google’s current troubleshooting guide says the canonical element “is not recommended for those who want to avoid duplication by syndication partners, because the pages are often very different.” Its recommended fix is for partners to block indexing of the syndicated copy. If you republish on Medium or a partner site, a cross-domain canonical is a request, and Google may not honour it.
Canonical tags and hreflang
Canonical and hreflang answer different questions. The canonical picks one URL among duplicates. Hreflang maps language and region versions that should each stay indexed. Google’s rule for combining them is one line in its canonical best practices: specify a canonical page in the same language, or the best substitute language if a same-language canonical does not exist.
In practice, every language version carries a self-referencing canonical and the full hreflang set:
<!-- On https://example.com/de/preise/ -->
<link rel="canonical" href="https://example.com/de/preise/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/pricing/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/preise/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/pricing/" />
The hreflang set is identical on every version, and each version must list itself as well as all others, with fully qualified URLs. Only the canonical changes from page to page.
Two edge cases decide most arguments. Translated pages are only duplicates if the main content stays untranslated, so a German page canonicalised to the English one is a mistake. Regional versions in the same language (US and UK English) are duplicates, and Google says to use both canonicalisation and hreflang for them. The conflicts and their fixes are covered in hreflang and canonical tags.
Canonical in HTTP headers
PDFs, Word files and other non-HTML documents have no <head>, so the canonical travels in the response instead. Google supports a Link HTTP header with rel="canonical" for documents it indexes, including non-HTML files, in web search results only. The format is:
Link: <https://example.com/guides/canonical-tags/>; rel="canonical"
A common use is a PDF version of an article that should defer to the HTML page. On nginx, attach the header in the location that serves the file:
location = /downloads/canonical-tags.pdf {
add_header Link '<https://example.com/guides/canonical-tags/>; rel="canonical"';
}
On Apache with mod_headers enabled:
<Files "canonical-tags.pdf">
Header set Link "<https://example.com/guides/canonical-tags/>; rel=\"canonical\""
</Files>
Check it with curl -I. This is the response from a small Node server set up to send that header for this article:
$ curl -sI http://localhost:8099/downloads/canonical-tags.pdf
HTTP/1.1 200 OK
Content-Type: application/pdf
Link: <https://example.com/guides/canonical-tags/>; rel="canonical"
The header also works on HTML pages, but pick one method: using both is supported but more error prone, because the two can drift apart.
How frameworks set canonical tags
On most sites a layout, a metadata API or a plugin writes the canonical from configuration, so it is only as correct as that config.
Next.js (App Router)
Next.js sets the canonical through the alternates.canonical field of the Metadata API. Set metadataBase once in the root layout so fields can use relative paths:
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
};
Then set the canonical per route, usually in generateMetadata for dynamic pages:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
type Props = { params: Promise<{ slug: string }> };
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
return {
alternates: {
canonical: `/blog/${slug}`,
languages: {
'en-GB': `/blog/${slug}`,
'de-DE': `/de/blog/${slug}`,
},
},
};
}
Next.js composes the relative path with metadataBase into an absolute URL, and a relative path without metadataBase causes a build error. The same alternates object outputs hreflang links from languages.
The trap is inheritance. Metadata from nested segments is shallowly merged, and nested objects are replaced by the last segment that defines them. Two consequences follow. If the root layout sets alternates: { canonical: '/' }, every route that does not define its own alternates inherits a canonical pointing at the home page. And a page that sets alternates.canonical without languages drops the hreflang links a parent defined. Set alternates per route, completely. The wider Next.js set-up is in Next.js SEO.
Astro
In Astro the canonical usually lives in a shared layout that every page uses. The Astro docs give the pattern:
---
// src/layouts/BaseLayout.astro
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---
<link rel="canonical" href={canonicalURL} />
Astro.site comes from the site option in astro.config.mjs. For this article I built a one-page Astro project (Astro 5.18.2) three ways and read the generated HTML:
| Config | What the build did |
|---|---|
site: 'https://example.com', default build format | <link rel="canonical" href="https://example.com/blog/post/"> |
No site set | Build failed with Invalid URL, because Astro.site was undefined |
No site, canonical built from Astro.url instead | <link rel="canonical" href="http://localhost:4321/blog/post/"> |
site set, build: { format: 'file' } | <link rel="canonical" href="https://example.com/blog/post.html"> |
The failed build is the good outcome, because you notice it. The third row is the dangerous one: a “fix” that swaps Astro.site for Astro.url builds cleanly and ships a localhost canonical on every page. The docs confirm why: prerendered pages receive a localhost URL during builds if site is not configured.
The fourth row shows the other thing to line up. The canonical follows your build.format and trailing-slash settings, so check they match the URLs you actually link to and list in the sitemap. This site runs on Astro, and its layout builds the canonical from the path and a fixed site URL, with an optional per-page override. More detail is in Astro SEO.
WordPress
WordPress core prints a canonical through rel_canonical(). Its source shows it only runs for singular queries (posts, pages, attachments), so core prints nothing on archives or the home page listing. Since version 4.6 it takes its URL from wp_get_canonical_url(), and you can change that URL with the get_canonical_url filter:
add_filter( 'get_canonical_url', function ( $canonical_url, $post ) {
$override = get_post_meta( $post->ID, 'canonical_override', true );
return $override ? $override : $canonical_url;
}, 10, 2 );
Most sites run an SEO plugin instead, which takes over canonical output. In Yoast SEO, for example, the plugin adds a canonical to every post and page by default, and you override it in the sidebar under Advanced, in the Canonical URL field. Developers can use the wpseo_canonical filter, and Yoast does not print a canonical on pages marked noindex, so a missing canonical there is expected behaviour.
The WordPress-specific risk is duplication: a theme that hard-codes a canonical in header.php, plus a plugin. Check the source for more than one tag after any theme or plugin change.
Client-side JavaScript
If the canonical has to come from JavaScript, Google’s rules are specific. Set it in the HTML if you can. If the HTML already has one, JavaScript must not change it to a different URL. If you cannot set it in the HTML, leave it out of the HTML entirely and inject it with JavaScript. Google picks up an injected canonical when it renders the page. Render timing and the other JavaScript risks are covered in the JavaScript SEO guide.
Where the canonical stops and removal starts
A canonical keeps content in the index under a preferred URL. If the real goal is that a page should not be in search at all, that is a different tool, and Google does not recommend using noindex to steer canonical selection within a site. When you are choosing between the two, the decision rules are in canonical vs noindex.
9 canonical tag mistakes
Each of these produces a tag that looks fine in the template and does nothing, or does damage, in practice.
1. A relative or scheme-less URL
href="example.com/page" has no scheme, so it is a relative path. Google’s 2013 mistakes post walks through exactly this: it resolves to https://example.com/example.com/page, and Google may ignore it. Always write the full https:// URL.
2. More than one canonical on the page
A theme prints one, an SEO plugin prints another, a tag manager adds a third. When a page specifies more than one, all of them will be ignored, per the same post. Search the rendered source for rel="canonical" and expect exactly one hit.
3. The canonical ends up outside the <head>
The source can be correct and the parsed page still wrong. If an <img> or <iframe> sits in the <head> above the canonical, Google assumes the <head> has ended and stops reading further elements. A canonical placed in the <body> is disregarded. Keep the canonical near the top of the <head> and check the rendered HTML, not only the template.
4. Paginated pages canonicalised to page 1
Pages 2 onwards list different items, so they are not duplicates. Google’s pagination guidance is direct: do not use the first page of a paginated sequence as the canonical page; give each page its own canonical. The full pattern is in pagination SEO.
5. A canonical target that does not return a clean 200
The target has to exist and be indexable. Google’s checklist says to confirm the target is not an error or soft 404 and does not carry a noindex robots tag. Point at the final URL, not one that redirects, so Google is not resolving two signals at once. If a target serves a thin “not found” page with a 200 status, fix that first: see soft 404 errors.
6. Signals that disagree with the tag
The canonical says /pricing/, the sitemap lists /pricing, and the navigation links to /pricing?ref=nav. Google asks you not to specify different URLs as canonical through different methods and to link internally to the canonical URL. The same applies to protocol: an HTTPS page with a canonical pointing to HTTP is one of the conditions that stops Google preferring the HTTPS version.
7. JavaScript rewriting the canonical
A client-side router updates the title and, with it, the canonical, so the server HTML says one URL and the rendered page says another. Google’s JavaScript guidance says not to use JavaScript to change the canonical to something other than the URL in the original HTML. Set it on the server, or only on the client, never both.
8. Blocking the duplicates you are canonicalising
Disallowing parameter URLs in robots.txt feels tidy, but Googlebot cannot read a canonical on a page it may not fetch. Google says not to use robots.txt for canonicalisation, since disallowed URLs can still be indexed without their content. Leave the duplicates crawlable so the tag is seen.
9. One canonical for many different pages
This is the template mistake. A layout hard-codes the home page as canonical, a copied template still points at the original site, or a category page canonicalises to the article it features that day. Google describes all of these in its mistakes post: the category page disappears from results because you told Google the article is the preferred version of it. The Next.js inheritance trap above is the modern form of the same bug.
How to check which canonical Google chose
Your declared canonical and Google’s selected canonical are two different fields, and only one of them decides what is indexed.
- Read what you send. View the rendered HTML and count the canonical elements. For files, run
curl -Iand read theLinkheader. - Read what Google chose. URL Inspection in Search Console shows the Google-selected canonical. If it differs from yours, ask whether Google’s choice is actually better for searchers before you fight it.
- Look for the technical cause. Google’s troubleshooting guide names the usual suspects: CMS or plugin canonicals pointing at undesired URLs, misconfigured servers, missing hreflang and syndicated copies.
- Wait before you change it again. After a fix, Google may hold pages in a duplicate cluster for up to two weeks, and pages split out faster when their content is clearly different.
To check a whole site, a crawler reports missing, multiple, non-200 and non-indexable canonicals in one run; the options are compared in best technical SEO tools. Canonical checks are part of the indexation section of the technical SEO audit checklist. If a URL sits under “Alternate page with proper canonical tag” in the Page Indexing report, the tag is working; that status explained covers the rare case where it is not.
Canonicals get rewritten in bulk during replatforms and domain moves, which is when these mistakes arrive all at once. The site migration checklist covers the pre-launch canonical inventory. If you would rather have the crawl-and-index layer set up and checked by someone who writes the code, that is part of GEO and technical SEO.
Frequently asked questions
What is a canonical tag in SEO?
A canonical tag is an HTML <link rel="canonical"> element in a page’s <head> that names the preferred URL for a piece of content available at several URLs. Search engines use it to decide which URL to index and show, and to consolidate signals such as links from the duplicates. Google treats it as a strong hint rather than a rule.
Should every page have a self-referencing canonical tag?
Yes, in most cases. Google recommends a self-referential canonical on the canonical page itself, and it protects the page from parameter variants such as ?gclid= or UTM tags that you do not control. Build it from the clean URL path, not from the full request URL with its query string.
Can a canonical tag point to another domain?
Yes. Google has supported cross-domain canonicals since 2009, for example when a site moves and redirects are not possible. Map each URL to its equivalent rather than pointing everything at a home page. For syndicated content, Google now recommends that partners block indexing of their copy instead of relying on a canonical.
Does Google always follow the canonical tag?
No. A canonical is one signal among several, alongside redirects, sitemaps, internal links and HTTPS. If those disagree with the tag, or the pages are not really duplicates, Google may select a different canonical. URL Inspection in Search Console shows which URL Google chose.
How do I add a canonical tag to a PDF?
Send it as an HTTP response header, because a PDF has no HTML <head>. The header takes the form Link: <https://example.com/page/>; rel="canonical", set in your server or CDN configuration for that file. Confirm it with curl -I against the PDF URL.
Can a page have both a canonical tag and hreflang tags?
Yes, and multilingual pages should have both. Each language version carries a canonical pointing to itself plus the full set of hreflang alternates. Never canonicalise a translated page to another language version, because translated pages are not duplicates and the canonical would remove them from their market’s results.