Background
While auditing SEO for Oceanz.site, I found a subtle but serious issue: even though the sitemap had already been submitted to Google Search Console, only a few pages were discoverable in search, and most pages were not indexed at all.
The site stack looks like this:
- 16.2.6 App Routerï¼›
Static Export;URL Pathinternationalization (/en,/zh-cn);- Sitemap submitted to GSC;
After the site had been live for a while, I noticed the following in GSC:
- Almost no pages were actually indexed.
- site:oceanz.site returned only a very small number of pages.
At the same time, the Indexing -> Pages report in GSC showed a large number of warnings:
Duplicate, Google chose different canonical than user
Root Cause
If you ask an LLM, most answers suggest checking the Sitemap first and reviewing content quality. In my case, neither was the primary issue. The real problem was an incorrect Canonical configuration.
In the root layout of the App Router, I had configured default Metadata like this:
export const metadata = {
alternates: {
canonical: "https://www.oceanz.site/en",
},
}These settings were inherited by all child routes. As a result, for an article page like:
/en/articles/nextjs-canonical-hreflang-google-indexing
Google saw:
<link rel="canonical" href="https://www.oceanz.site/en" />
<link rel="alternate" hreflang="en" href="https://www.oceanz.site/en" />
<link rel="alternate" hreflang="zh-CN" href="https://www.oceanz.site/zh-cn" />This effectively tells that the current article is not an independent page. Its canonical source is the English homepage (canonical points to /en), and its Chinese counterpart is the Chinese homepage (hreflang points to /zh-cn).
Following those signals, may merge the article content into the homepage and skip indexing the article URL itself. Since all articles point to the homepage, Google can interpret dozens of pages as duplicates of one page, triggering "Duplicate, Google chose different canonical than user" (because Google rejects your hint and picks its own canonical) or simply dropping those pages from indexing.
Solution
Each article page must point to its own canonical URL, not to the homepage.
canonical and languages should be generated from the actual current route path (slug).
Because this site uses Next.js static export (Static Export), generateMetadata cannot directly read runtime request data (such as headers() or pathname). The most reliable approach is to build URLs from route params (params).
// Example: [locale]/articles/[slug]/page.tsx
import { Metadata } from 'next';
type Props = {
params: Promise<{ locale: string; slug: string }>;
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale, slug } = await params;
// 1) Load current article metadata (e.g. title/description from MDX)
// const article = await getArticleData(slug, locale);
const baseUrl = 'https://www.oceanz.site';
// Normalize locale tags (GSC is case-sensitive; use consistent casing)
const currentLocale = locale === 'zh-cn' ? 'zh-cn' : 'en';
// Build the actual relative path for this page
const relativePath = `/${currentLocale}/articles/${slug}`;
return {
metadataBase: new URL(baseUrl),
title: "Article Title", // article.title
description: "Article Description", // article.description
alternates: {
// Key fix 1: canonical must point to this page's own dynamic URL
canonical: relativePath,
// Key fix 2: hreflang must point to the corresponding locale versions of this article
languages: {
'en': `/en/articles/${slug}`,
'zh-CN': `/zh-cn/articles/${slug}`,
'x-default': `/en/articles/${slug}`, // Recommended: provide a default locale
},
},
};
}After the fix, the expected output should look like this:
<link rel="canonical" href="https://www.oceanz.site/en/articles/nextjs-canonical-hreflang-google-indexing" />
<link rel="alternate" hreflang="en" href="https://www.oceanz.site/en/articles/nextjs-canonical-hreflang-google-indexing" />
<link rel="alternate" hreflang="zh-CN" href="https://www.oceanz.site/zh-cn/articles/nextjs-canonical-hreflang-google-indexing" />
<link rel="alternate" hreflang="x-default" href="https://www.oceanz.site/en/articles/nextjs-canonical-hreflang-google-indexing" />Canonical
Common terms:
- Canonical URL
- Canonical Tag
- Canonical Link Element
Canonical affects indexing ownership. It tells search engines which URL should be treated as the authoritative version of the page.
Basic syntax
<link rel="canonical" href="https://example.com/page" />Place it in the page <head> and use an absolute URL.
Main problem it solves: the same content may be accessible via multiple URLs, which can be seen as duplicate content and split ranking signals.
- Parameterized URLs:
/article?utm_source=newsletter,/article?ref=twitter - Duplicate URL forms:
wwwvs non-www, HTTP vs HTTPS, trailing slash variants, etc. - Signal consolidation: concentrate split ranking signals into the specified canonical URL
Other common use cases
- Self-referencing canonical: recommended even when no duplicates exist, to prevent accidental duplication from external crawling paths
- Cross-domain republishing: point canonical to the original source URL
- Paginated content: in some strategies, declare page 1 as canonical
Important notes
- Use only one canonical tag per page; multiple canonical tags are often ignored.
- Canonical is a hint, not a strict command, so search engines may override it.
- Canonical works well together with 301 redirects; they are complementary.
Comparison with related methods
| Method | Enforceability | Typical use case |
|---|---|---|
canonical tag | Advisory | Duplicate-content declaration |
| 301 redirect | Strong | Permanent URL migration |
noindex | Strong | Prevent indexing of a page entirely |
hreflang | Advisory | Distinguish localized language versions |
Hreflang
hreflang is used to establish relationships between language versions of a page.
Common terms:
- Hreflang Annotation
- Alternate Language Tag
- Language Alternate Link
The core difference from Canonical: it does not define the canonical page and does not consolidate ranking signals. It only tells search engines that these pages are language variants of the same content.
Basic syntax
Place these tags in the <head> of every language version, and make each language version reference all others:
- English page:
<link rel="canonical" href="https://example.com/en/page" />
<link rel="alternate" hreflang="en" href="https://example.com/en/page" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/page" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page" />- Chinese page:
<link rel="canonical" href="https://example.com/zh-cn/page" />
<link rel="alternate" hreflang="en" href="https://example.com/en/page" />
<link rel="alternate" hreflang="zh-CN" href="https://example.com/zh-cn/page" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page" />Core principles
- Each language page should have a canonical that points to itself; language relationships are expressed via hreflang.
- Do not point the Chinese page canonical to the English page.
- Do not point all language pages' canonicals to the homepage.
- Use
x-defaultfor the fallback/default version (usually English or a language selector page).
About "Google chose different canonical than user"
This status does not always mean your canonical is wrong. It means you declared one canonical URL, but Google ultimately selected a different URL as canonical.
If this status appears on many pages, check for conflicts across the following signals:
canonicaltagshreflangannotations- URLs included in sitemap
- Redirect rules
- Internal linking structure
Conclusion
For multilingual sites built with Next.js App Router, canonical misconfiguration is easy to overlook. It is especially common when static metadata is defined in the root layout, causing all pages to inherit the same canonical and making Google treat many pages as duplicates. If your site shows symptoms like:
- Abnormally low indexed-page count;
- Duplicate, Google chose different canonical than user;
- Language versions missing from index, or indexed in unusually low numbers;
the first thing to verify is whether the final rendered canonical and hreflang tags are correct on each page.