Debugging Next.js Multilingual Indexing Failures: Incorrect Canonical and Hreflang Configurations Prevent Google Indexing

After deploying a multilingual site with Next.js and submitting it to Google Search Console, I kept seeing 'Duplicate, Google chose different canonical than user.' The root cause turned out to be incorrect Canonical and Hreflang settings, which prevented proper indexing.

Canonical URLCanonical TaghreflangGoogle Search ConsoleGoogle chose different canonical than userDuplicate Google chose different canonical than userGoogle indexing issueGoogle not indexing pagesNext.js App Router SEOInternational SEOMultilingual SEOi18n SEOStatic Export SEONext.js Static ExportNext.js multilingual siteCanonical configurationHreflang configurationGoogle indexingWebsite indexingMultilingual websiteInternationalized SEOWebsite optimization

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 Path internationalization (/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: www vs 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

MethodEnforceabilityTypical use case
canonical tagAdvisoryDuplicate-content declaration
301 redirectStrongPermanent URL migration
noindexStrongPrevent indexing of a page entirely
hreflangAdvisoryDistinguish 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-default for 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:

  • canonical tags
  • hreflang annotations
  • 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.

No table of contents