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 或 noindex | return 链接指向了非 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 把 zh 和 zh-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 URLs 和 Reports > 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 两字母码(en、zh),可选带 ISO 3166-1 地区码(en-US、zh-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 年就移除了),用两种方式确认修好:
- 逐 URL —— URL Inspection 工具。在 Search Console 里粘 URL,打开 live test,检查 Google 能看到两边的 alternate。两个语言版本都跑一遍,每个都该列出另一个。
- 全站 —— 爬虫。跑 Screaming Frog,看
Reports > Hreflang > Missing Confirmation Links。修好后这份报告应当为空。同时确认Non-200 Hreflang URLs和Noindex 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.jp,hreflang="ja"可以指过去。相互对应规则同样适用:那个.jp页也必须指回来。 - 为什么
noindex的 return 页会打破整组? 指向 noindex(或 404 / 被重定向)页面的 return 链接不是有效确认,所以 Google 会忽略整个配对。hreflang 集合里的每个页面都必须返回200且可索引。 - 警告多久能消? 两边都正确后,下次重抓 1–2 周内消失。在 URL Inspection 里对关键 URL 点 “Request Indexing” 可以加快。