TL;DR
A canonical tells Google “this is the preferred URL for this content.” On a multilingual corporate site (PL/EN/DE) each locale gets its own self-canonical, you do not point the Polish page at the English URL as canonical. hreflang means “this is a translation”; canonical means “this is the original among technical duplicates.” In the Next.js App Router you set alternates.canonical + alternates.languages, add 301s for trailing slash/www, and strip UTMs from the indexed URL. Below: duplicate map, Metadata API code, hreflang↔canonical conflicts, and a GSC checklist.
Who is this for
- Owners of pl/en/de sites with duplicate warnings in GSC
- Next.js developers after a migration or URL restructure
- SEOs fighting www vs non-www, slash vs no-slash, UTMs in the index
- Teams whose preview/ISR creates extra URLs
- Companies with the “same offer” under three paths
Keyword
canonical url nextjs, duplicate content pl en de, hreflang vs canonical, trailing slash seo, alternates.canonical app router, preferred url 2026
Canonical vs hreflang, different jobs
| Mechanism | Question it answers | Example |
|---|---|---|
| Canonical | Which URL is primary for this same content/variant? | /en/services canonical vs ...?utm_source=linkedin |
| hreflang | Which URLs are translations of the same page? | /pl/oferta ↔ /en/services ↔ /de/leistungen |
| 301 redirect | Which host/path is the only HTTP destination? | www → apex, /services/ → /services |
Classic mistake: setting the Polish page’s canonical to English “because EN is the main market.” Google may ignore hreflang and consolidate onto EN, PL disappears from local results.
Correct:
- PL → canonical
https://example.com/pl/oferta - EN → canonical
https://example.com/en/services - DE → canonical
https://example.com/de/leistungen - All three → mutual
hreflang(+x-defaultif you use one)
Common duplicate sources on service sites
- www vs non-www and http vs https
- Trailing slash (
/en/servicesvs/en/services/) - Parameters (UTM,
fbclid, sorting) - Locale fallback (middleware serves EN on
/and/en) - Old URLs after redesign without 301s
- Blog pagination / tags with thin content
- Preview / draft indexed by accident
- Same CMS content under two slugs
Case studies in Branchly need one slug per locale and a stable canonical in metadata, do not invent a second “campaign URL.”
Next.js App Router, Metadata API
// app/[locale]/services/page.tsx
import type { Metadata } from 'next';
const BASE = 'https://devstudioit.com';
const paths = {
pl: '/pl/oferta',
en: '/en/services',
de: '/de/leistungen',
} as const;
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: keyof typeof paths }>;
}): Promise<Metadata> {
const { locale } = await params;
const path = paths[locale];
return {
title: '…',
description: '…',
alternates: {
canonical: `${BASE}${path}`,
languages: {
pl: `${BASE}${paths.pl}`,
en: `${BASE}${paths.en}`,
de: `${BASE}${paths.de}`,
'x-default': `${BASE}${paths.en}`,
},
},
};
}x-default is the fallback for unrecognized languages (often EN or a language picker), not “the most important market.”
Blog, shared slug, locale in path
canonical: https://devstudioit.com/en/blog/{slug}
hreflang pl/en/de → /{locale}/blog/{slug}Prefer path prefixes over ?lang=en as the canonical model.
Trailing slash, pick one convention
Next.js: trailingSlash: true | false in next.config.
| Convention | Canonical | Redirect |
|---|---|---|
| No slash (common for many apps) | /en/services |
/en/services/ → 301 |
| With slash | /en/services/ |
reverse |
Host one policy on DevStudioIT Cloud aligned with Next config. Mismatch = soft duplicates in GSC.
const nextConfig = {
trailingSlash: false,
};UTM and tracking parameters
Landing canonical without UTMs. Tracking stays in GA4; the index sees a clean URL.
| Incoming URL | Canonical |
|---|---|
/en/services?utm_source=linkedin |
/en/services |
/en/services?ref=partner |
/en/services (if identical) |
/en/services?plan=pro (different content) |
its own URL + canonical |
Conflicts Google tends to ignore
- Canonical A→B while B points back to A (loop)
- hreflang pointing at
noindexURLs - hreflang pointing at 404 / redirect chains
- Cross-domain canonical without a strong reason
- HTML canonical X while the sitemap only lists Y inconsistently
Rule: every URL in an hreflang cluster must be 200, indexable, self-canonical, with a full alternate set.
Diagnose in Search Console
- Page indexing → duplicate / “Google chose different canonical”
- URL Inspection → user-declared vs Google-selected canonical
- If Google disagrees: strengthen self-canonical, align content, point internal links at the preferred URL
| GSC signal | Action |
|---|---|
| Google chose different canonical | Check content, links, slash, www |
| Alternate page with proper canonical | OK, intentional consolidation |
| Duplicate, Google didn’t canonicalize | Fix canonical + 301s |
Internal links reinforce the canonical
A canonical is a hint, Google also weighs internal links. If the nav, blog, and footer point to /en/services/ while the canonical says /en/services, you send conflicting signals. After you pick a slash convention:
- update navigation, breadcrumbs, and CTAs
- regenerate the sitemap
- fix older CMS / case-study URLs in Branchly
On hosting (DevStudioIT Cloud), keep the 301 as close to the edge as possible, shorter chains mean cleaner crawls.
Rollout checklist
- One host policy (https + www/apex)
- One trailing-slash policy + 301
- Self-canonical on every public page
- Full hreflang pl/en/de (+ x-default)
- No UTMs in canonical
- Old URLs → 301 to new
- Staging noindex / separate domain
- Sitemap lists only canonical URLs
- After deploy: inspect 5–10 key pages
- Monitor the duplicate report for 2–4 weeks
- Align internal links with the canonical
- No canonical A↔B loops
FAQ
Does canonical replace a 301?
No. 301 moves users and link equity at HTTP level. Canonical is a hint for near-duplicate indexing.
Does every page need hreflang?
Only when an equivalent translation exists. A PL-only page should not fake an EN/DE cluster.
What about blog pagination?
Self-canonical on each paginated page or a view-all strategy, pick one. Avoid pointing every page at page=1 if page=2 has unique value.
Should JSON-LD repeat the canonical?
mainEntityOfPage / @id should match the canonical URL, consistency helps; mismatch confuses.
CTA
Duplicates across PL/EN/DE, slash chaos, or “Google chose a different canonical”?
- Book a canonical / hreflang audit, Metadata API, 301s, DevStudioIT Cloud
About the author
We build fast websites, web/mobile apps, AI chatbots and hosting setups — with a focus on SEO and conversion.
Recommended links
From theory to production — Branchly, our hosting stack and shipped work.
