Next.js 国际化网站索引失败排查:Canonical 与 Hreflang 配置错误导致 Google 不收录

使用 Next.js 进行国际化网站部署后,提交至 Google Search Consoel GSC ,提示:Duplicate, Google chose different canonical than user,排查后发现 Canonical 与 Hreflang 配置错误导致是 Google 不收录的原因之一

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 国际化网站Canonical 配置Hreflang 配置Google 索引网站收录多语言网站国际化 SEO网站优化

背景

最近在为Oceanz.site 做 SEO 排查时,发现一个比较隐蔽的问题:网站已经提交 Sitemap 至 Google Search Console,搜索引擎确仅能检索出个别页面,绝大多数页面没有进入索引,无法被检索。

网站技术栈如下:

  • 16.2.6 App Router;
  • Static Export 静态导出;
  • URL Path 国际化(/en、/zh-cn);
  • GSC 已提交 Sitemap;

上线一段时间后,在 GSC 中发现:

  • 实际收录页面数量几乎没有;
  • site:oceanz.site 只能搜索到极少量页面;

同时在 GSCIndexing -> Pages 中出现大量提示:

Duplicate, Google chose different canonical than user

原因

如果咨询 LLM ,大部分会建议先检查 Sitemap,同时审视内容质量,但是以上都不是原因,主要的原因是 Canonical 配置错误。

由于我在 App Router 的根布局中配置了默认 Metadata

export const metadata = {
  alternates: {
    canonical: "https://www.oceanz.site/en",
  },
}

这些配置被所有子路由继承。结果 访问下面这样的文章:

/en/articles/nextjs-canonical-hreflang-google-indexing

看到的却是:

<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" />

这相当于亲口告诉 当前这篇文章页不是独立页面,它的本体/规范页面其实是英文首页(canonical 指向了 /en)。中文对应版本也是中文首页(Hreflang 指向了 /zh-cn)。

听了指挥,就会把这篇文章的内容直接“合并”到首页里,不给文章页建立索引。由于所有文章都指向了首页, 就会认为网站有几十个页面全在“抄袭”首页,从而触发了之前看到的 "Duplicate, Google chose different canonical than user"(因为 觉得不对,于是自己瞎猜了一个)或者直接判定为内容重复而不予收录。

解决方式

让所有文章页都指向自己的规范页面,而不是首页。

canonicallanguages 对应的链接根据当前页面实际的路径(slug)自动拼接。

由于使用的是 Next.js 静态导出(Static Export),在 generateMetadata 中无法直接读取运行时(Runtime)的请求头(比如 headers() 或 pathname),所以最标准、最稳妥的做法是接收路由参数(params)来动态拼接 URL。

例如 [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. 正常加载当前文章的元数据(比如从 mdx 中读取的 title, description)
  // const article = await getArticleData(slug, locale);
 
  const baseUrl = 'https://www.oceanz.site';
  // 规范化语言标签(GSC 对大小写敏感,建议全小写,或严格遵循标准)
  const currentLocale = locale === 'zh-cn' ? 'zh-cn' : 'en'; 
  
  // 动态拼接当前页面的实际相对路径
  const relativePath = `/${currentLocale}/articles/${slug}`;
 
  return {
    metadataBase: new URL(baseUrl),
    title: "文章标题", // article.title
    description: "文章描述", // article.description
    alternates: {
      // 核心修改 1:Canonical 必须指向自己当前的动态完整 URL
      canonical: relativePath, 
      
      // 核心修改 2:Hreflang 必须指向当前文章“对应的其他语言版本”的动态完整 URL
      languages: {
        'en': `/en/articles/${slug}`,
        'zh-CN': `/zh-cn/articles/${slug}`,
        'x-default': `/en/articles/${slug}`, // 推荐加上默认语言版本
      },
    },
  };
}

调整后的预期应类似如下:

<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

专业术语:

  • Canonical URL(规范 URL)
  • Canonical Tag(规范标签)
  • Canonical Link Element(规范链接元素)

会影响索引归属,用于声明当前页面的权威 URL 是哪一个。

基本语法

<link rel="canonical" href="https://example.com/page" />

放在页面的 <head> 中,使用绝对路径。

主要解决的问题: 同一内容可能通过多个 URL 被访问,搜索引擎会将其视为重复内容并分散权重:

  • 参数页面/article?utm_source=newsletter/article?ref=twitter
  • 重复 URLwww 与非 www、HTTP 与 HTTPS、末尾斜杠等
  • 权重归并:将分散的页面权重集中到指定的规范 URL 上

其他使用场景

  • 自引用:即使没有重复版本,也建议加上自引用 canonical 以防止外部抓取产生的重复问题
  • 跨域内容:转载或内容聚合时,指向原始来源的 URL
  • 分页内容:将第 1 页声明为规范版本

注意事项

  • 每个页面只放一个 canonical 标签,多个时搜索引擎会忽略
  • Canonical 是「建议」而非「指令」,搜索引擎可能忽略
  • 配合 301 重定向效果更佳,两者不冲突

与相关技术对比

方法强制性适用场景
canonical 标签建议性重复内容声明
301 重定向强制性永久迁移 URL
noindex强制性完全不索引该页
hreflang建议性多语言页面区分

Hreflang

用于建立跨语言页面之间的关联。

专业术语:

  • Hreflang Annotation(多语言标注)
  • Alternate Language Tag(语言替代标签)
  • Language Alternate Link(语言替代链接)

与 Canonical 的核心区别:不指定规范页面,也不进行权重归并,只负责告诉搜索引擎「这些页面是同一内容的不同语言版本」。

基本语法

放在每个语言页面的 <head> 中,所有语言版本互相声明:

  • 英文页面:
<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" />
  • 中文页面:
<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" />

核心原则

  • 每个语言页面的 Canonical 指向自己,语言版本之间通过 Hreflang 建立关联
  • 不要让中文页的 Canonical 指向英文页
  • 不要让所有语言页面的 Canonical 都指向首页
  • x-default 用于声明默认/兜底版本(通常是英文或语言选择页)

关于 "Google chose different canonical than user"

这个状态不一定意味着 Canonical 写错,它表示的是:你声明了一个 Canonical,但 Google 最终选择了另一个 URL 作为规范页面。

如果大量页面出现这个状态,应重点排查以下几项是否存在冲突:

  • canonical 标签
  • hreflang 标注
  • Sitemap 收录的 URL
  • 重定向(Redirect)规则
  • 内链结构指向

结论

对于 Next.js App Router 国际化网站来说,Canonical 配置错误是一个非常容易忽略的问题。尤其是在根布局中配置静态 Metadata 时,很容易让所有页面继承同一个 Canonical,最终导致 Google 将大量页面识别为重复内容。如果网站出现:

  • 收录数量异常少;
  • Duplicate, Google chose different canonical than user;
  • 多语言版本不收录,或者收录数量异常少;

那么第一件事应该检查的,就是页面最终输出的 Canonical 与 Hreflang 是否正确。

暂无目录