Hreflang "No Return Tags":补上缺失的双向链接

爬虫报你 hreflang 簇 "missing return tags":A 指向 B,但 B 没指回 A。让每个引用都相互对应,Google 才不会整组忽略。

hreflang 爬虫(Screaming Frog、Sitebulb、Ahrefs)报 “missing return links”——也就是 Google Search Console 历史上说的 “no return tags”。你的英文页正确地用 <link rel="alternate" hreflang="zh" href="..."> 指向中文译文,但中文页没有指回英文。hreflang 规则要求双向对应:A 说”我的 zh 译文是 B”,那 B 必须说”我的 en 译文是 A”。return tag 一缺,Google 就把整组标注当成不可靠,悄悄整体忽略。

最快修法:用一个共享 layout 输出 hreflang,列出簇里每一种语言(包含当前页自指),两边用逐字节相同的 URL 和相同的语言码。一个模板 = 保证相互对应。细节见下。

提醒(截至 2026 年 6 月):Google 已经不在 Search Console 任何地方报 hreflang 错误。过去 “No return tags” 出现的那个 International Targeting 报告,已在 2022 年 9 月废弃并移除。如果某篇旧文叫你”去 International Targeting 看看”,那个报告早没了。现在诊断要用 URL Inspection 工具(逐 URL)或第三方爬虫(全站)。见下文 确认已修好

症状很隐蔽,因为不会有明显报错:中文页对英文 query 排名,而英文版本明明存在;或者某国 SERP 里冒出错语言的 URL。原因几乎总是下面七种里的某一种。

你属于哪一类?

你看到的现象最可能的原因跳转
对某一语言 grep hreflang 什么都没有那个模板根本没输出 hreflang原因 1
两边都有标签,爬虫还是报URL 字符串不是逐字节相同原因 2
一边写 zh,另一边写 zh-CN语言码不一致(或用了 _ 而非 -原因 3
return 那一边的 URL 是 404、301 或 noindexreturn 链接指向了非 200 / 不可索引的页原因 4
两边都有标签,但其中一页 canonical 指向别处跨语言 canonical 覆盖了 hreflang原因 5
簇里有 3+ 语言,只有部分配上对缺自指,或缺某个簇成员原因 6
HTML head 和 sitemap 里的标签不一致两个声明来源互相矛盾原因 7

常见原因

1. 一个模板输出了 hreflang,另一个忘了

英文 layout 写了到每种语言版本的 hreflang。中文 layout 是从单语模板拷过来的,hreflang 块从没加上。

怎么判断curl -s https://yoursite.com/zh/articles/foo/ | grep -i hreflang。输出为空,说明中文模板完全没输出 hreflang,自然不可能有 return 链接。

2. Hreflang URL 跟实际服务的 URL 不一致

英文页声明 <link rel="alternate" hreflang="zh" href="https://yoursite.com/zh/articles/foo/">。但中文页实际服务在 https://yoursite.com/zh/articles/foo(没斜杠)或 https://www.yoursite.com/zh/articles/foo/(不同 host)。Google 是按字符串精确比对往返的,闭不上环。

怎么判断:把每一边声明的 alternate URL,和该页实际服务的 canonical URL 对照。trailing slash、www 有无、http/https——任一处不同都会让匹配失败。

3. 语言码不一致

英文页写 hreflang="zh",中文页写 hreflang="zh-CN"。Google 把 zhzh-CN 当成不同标注,不会配对。还有一个很常见的变体:用了下划线zh_CN)而不是连字符zh-CN)——下划线形式无效,会被直接丢弃。

怎么判断:grep 整簇的每个 hreflang 值。语言码两端必须完全字符串相等,而且要用连字符,绝不能用下划线。

4. Return 那一边是 404、301 或 noindex

英文页链到中文页,但中文页被删了(404)、重定向到别处(301/302),或者带了 <meta name="robots" content="noindex">。指向非 200 或不可索引页面的 return 链接不算数:return 目标被 noindex 时 Google 会忽略整组 hreflang 关系,而重定向 / 404 上根本没有标签可读。

怎么判断:每个 hreflang 目标跑 curl -sI https://yoursite.com/zh/articles/foo/ 确认是 200。再检查该页不是 noindex(在 head 里 grep robots / noindex)。Screaming Frog 把这些归在 Reports > Hreflang > Non-200 Hreflang URLsReports > Hreflang > Noindex Return Links

5. Canonical 指向了别的 URL

中文页有正确指回英文的 hreflang,但它同时<link rel="canonical" href="..."> 指向第三个 URL。Google 先解析 canonical,再从 canonical 目标上读 hreflang——而那个目标没有 return tag。跨语言 canonical(中文页 canonical 到英文)是这个 bug 最常见的形态。

怎么判断:簇里每个成员看它的 rel=canonical,必须指向自己。指向别处就先修 canonical,再动 hreflang。

6. 缺自指,或某个簇成员缺失

每个语言版本都必须包含指向自己的 hreflang。英文页要同时有 hreflang="en"(自指)和 hreflang="zh"(其他)。语言达到 3+ 种时,每页都要列出所有成员。漏掉一个,那个子配对的 return 检查就过不了。

怎么判断:view source,数 hreflang 条数。条数应等于语言版本数,含当前页本身。

7. HTML 和 sitemap 的 hreflang 不一致

有的团队只把 hreflang 放 sitemap.xml,有的放 HTML <head>。两处都有又不一致时,Google 可能读其中一个、漏掉另一个里的 return 链接。只保留一个事实来源。

怎么判断:把同一 URL 在 sitemap 里的 <xhtml:link> 块,和 HTML 里的 <link rel="alternate"> 标签对照。必须完全一致,或者其中一个干脆不要有。

最短修复路径

第 1 步:选一种声明方式,坚持到底

三种合法方式——只选一种,绝不混用两种:

  • HTML <head> 标签(内容站最常用)
  • HTTP 响应头Link: header——PDF 等非 HTML 文件唯一能用的方式)
  • XML sitemap <xhtml:link>

走 HTML 方式时,每个语言版本都必须包含所有语言(含自己)的 hreflang。

第 2 步:用一个共享 layout 输出 hreflang

写一个 helper,输入当前文章的 translationKey,输出完整簇。一个模板就保证了相互对应,因为每页渲染的是同一份列表:

---
const { translationKey, currentSlug } = Astro.props;
const translations = (await getCollection('articles'))
  .filter(a => a.data.translationKey === translationKey);
const SITE = 'https://yoursite.com';
---
{translations.map(t => (
  <link rel="alternate" hreflang={t.data.lang}
    href={`${SITE}/${t.data.lang}/articles/${t.data.urlSlug}/`} />
))}
<link rel="alternate" hreflang="x-default"
  href={`${SITE}/en/articles/${currentSlug}/`} />

因为列表是从簇里推导出来的(不是每页手写),每个成员都会链到其他每个成员以及自己。

第 3 步:URL 格式完全一致

锁定一种 canonical URL 形式——protocol、host 带不带 www、带不带 trailing slash——并到处都用它:

# 对照每页声明了什么
for url in \
  "https://yoursite.com/en/articles/foo/" \
  "https://yoursite.com/zh/articles/foo/"; do
  echo "== $url"
  curl -s "$url" | grep -oE 'hreflang="[^"]+" href="[^"]+"'
done

确认每个声明的 hreflang URL 正好是那个语言版本的 canonical URL。

第 4 步:语言码统一

用 ISO 639-1 两字母码(enzh),可选带 ISO 3166-1 地区码(en-USzh-CN)。选一种风格,永不混用。永远用连字符,绝不用下划线。中文:需要区分字体时用 zh-Hans(简体)和 zh-Hant(繁体);否则用纯 zh 即可。

第 5 步:先修 canonical

每个簇成员必须 rel=canonical自己,绝不能到另一种语言:

<!-- 错:ZH 页 canonical 到 EN -->
<link rel="canonical" href="https://yoursite.com/en/articles/foo/">

<!-- 对:ZH 页 canonical 到自己 -->
<link rel="canonical" href="https://yoursite.com/zh/articles/foo/">

canonical 和 hreflang 一旦矛盾,canonical 胜出,你的 hreflang 就白写了。先修这一步,再回头查 return tag。

第 6 步:在 CI 里校验簇完整性

让缺成员的问题上线前就被拦下:

// scripts/check-hreflang.mjs
import { glob } from 'glob';

const errors = [];
const files = glob.sync('src/content/articles/**/*.mdx');
const byKey = {};

for (const f of files) {
  const fm = parseFrontmatter(f);            // 你自己的 frontmatter 解析器
  (byKey[fm.translationKey] ??= []).push({ file: f, lang: fm.lang });
}

for (const [key, members] of Object.entries(byKey)) {
  if (members.length < 2) continue;          // 单语,无所谓
  const langs = new Set(members.map(m => m.lang));
  // 每种语言应当只有一个文件,且至少 2 种语言
  if (langs.size !== members.length) {
    errors.push(`Duplicate lang in cluster ${key}`);
  }
}
if (errors.length) { console.error(errors.join('\n')); process.exit(1); }

它不渲染 HTML,但能保证每个 translationKey 簇都完整、无歧义——这正是共享 layout 正确输出的前提。

第 7 步:确认已修好 {#validate-its-fixed}

因为 Search Console 已不再报 hreflang(International Targeting 2022 年就移除了),用两种方式确认修好:

  1. 逐 URL —— URL Inspection 工具。在 Search Console 里粘 URL,打开 live test,检查 Google 能看到两边的 alternate。两个语言版本都跑一遍,每个都该列出另一个。
  2. 全站 —— 爬虫。跑 Screaming Frog,看 Reports > Hreflang > Missing Confirmation Links。修好后这份报告应当为空。同时确认 Non-200 Hreflang URLsNoindex Return Links 也干净。

排名以及任何错语言的 SERP 结果,通常在下次重抓后 1–2 周内归位。关键页面可以在 URL Inspection 里点 “Request Indexing” 催一次重抓。

哪些情况不是你操作错了

如果译文是延迟发布的——今天上英文,下周才上中文——爬虫会在这个缺口期报缺 return 链接。这是预期的,两边都上线后会自动消失。别去加一个指向尚未返回 200 的 URL 的 hreflang。

容易误判的情况

误当成纯 canonical 问题。两者会互相影响:canonical 错了,正确的 hreflang 也会失效。永远先修”canonical 指向自己”,再回头查 return tag——否则你会一直在改 Google 根本读不到的 hreflang。

预防建议

  • 用一个共享 layout helper 输出 hreflang;绝不每页手写。
  • CI 校验:每个 translationKey 簇在各语言文件里都齐全。
  • 整站统一一种 canonical URL 形式(protocol、host、trailing slash)。
  • 每个语言版本 canonical 都指向自己。
  • 每季度用 Screaming Frog 或 Sitebulb 重抓一次,清空 Missing Confirmation Links

FAQ

  • Google Search Console 还会显示 “No return tags” 吗? 不会了。当年报这个的 International Targeting 报告 2022 年 9 月已废弃并移除。Google 仍在用 hreflang,只是不再报它的问题。改用 URL Inspection 或爬虫来验证。
  • 需要 x-default 吗? 建议有,非必需。它告诉 Google 在没有匹配语言时该给哪个 URL——对全球落地页或带语言选择器的页面有用。不写它不会引发 “no return tag” 问题。
  • Hreflang 能指向不同域名吗? 能。如果你的日文站在 yoursite.jphreflang="ja" 可以指过去。相互对应规则同样适用:那个 .jp 页也必须指回来。
  • 为什么 noindex 的 return 页会打破整组? 指向 noindex(或 404 / 被重定向)页面的 return 链接不是有效确认,所以 Google 会忽略整个配对。hreflang 集合里的每个页面都必须返回 200 且可索引。
  • 警告多久能消? 两边都正确后,下次重抓 1–2 周内消失。在 URL Inspection 里对关键 URL 点 “Request Indexing” 可以加快。

相关阅读

标签: #SEO #排查 #收录 #Search Console #hreflang #international