Search Console → “International Targeting” 或 “Pages → Hreflang” 报 “No return tags” 或 “Hreflang annotation has no return tag”。你的英文页正确用 <link rel="alternate" hreflang="zh"> 指向中文译文,但中文页没有指回英文。Google 的 hreflang 规则要求双向确认:A 说”我的译文是 B”,那 B 必须说”我的译文是 A”。否则 Google 当整组标注不可靠,整体忽略。
症状可能很微妙:中文页对英文 query 排名,而英文版本明明存在;或者某国 SERP 里出现错语言版本。修法是让每个 hreflang 引用都是相互的。
常见原因
1. EN 模板输出 hreflang,ZH 模板忘了加
英文页 layout 写了到所有语言版本的 hreflang。中文页 layout 是从单语模板拷过来的,hreflang 代码从没加。
怎么判断:curl https://yoursite.com/zh/articles/foo/ | grep hreflang。空就是中文模板完全没 hreflang。
2. Hreflang URL 跟页面实际服务 URL 不一致
EN 页写 <link rel="alternate" hreflang="zh" href="https://yoursite.com/zh/articles/foo/">。ZH 页实际是 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 语言码不一致
EN 写 hreflang="zh",ZH 写 hreflang="zh-CN"。Google 认为 zh 和 zh-CN 是不同标注,不会配对。
怎么判断:grep 整簇的 hreflang 值。必须完全字符串相等。
4. 一边有 hreflang,另一边 404 / 301
EN 链 ZH。ZH 被删了(404)或重定向(301)。return tag 不可能存在于不存在的页上。
怎么判断:每个 hreflang 目标 curl -I 确认 200 OK。其他状态都打破簇。
5. HTML head 有 hreflang,但 canonical 指向了第三个 URL
ZH 页有指回 EN 的 hreflang,但 ZH 页也有 <link rel="canonical" href="..."> 指向第三个 URL。Google 先按 canonical 走、在 canonical 目标上读 hreflang——而那个目标没有 return tag。
怎么判断:簇里每个成员看它的 canonical。canonical 必须指向自己(或 hreflang 必须放在 canonical 目标上)。
6. 自指 hreflang 缺失
每个语言版本都必须包含指向自己的 hreflang。EN 页同时要有 hreflang="en"(自指)和 hreflang="zh"(其他)。缺自指就打破簇。
怎么判断:view source,数 hreflang 条数。应等于语言版本数,含当前页。
7. Sitemap 里声明了 hreflang,HTML 里也有但不一致
有的团队只把 hreflang 放 sitemap.xml,有的放 HTML <head>。两边都有但不一致时,Google 可能选其一、忽略 return tag。
怎么判断:同一 URL 在 sitemap 里的 hreflang 块和 HTML 里的 hreflang 标签对比。必须完全一致。
最短修复路径
第 1 步:选一种声明方式,不要混用
三种合法方式(只选一种):
- HTML
<head>标签(最常见) - HTTP 响应头(
Link:header) - XML sitemap
<xhtml:link>块
HTML 方式:每个语言版本都必须包含所有语言(含自己)的 hreflang。
第 2 步:用共享 layout 实现 hreflang
写一个 helper,输入当前文章的 translation key,输出完整簇:
---
const { translationKey, currentLang } = Astro.props;
const translations = await getCollection('articles', a =>
a.data.translationKey === translationKey
);
---
{translations.map(t => (
<link rel="alternate" hreflang={t.data.lang}
href={`https://yoursite.com/${t.data.lang}/articles/${t.data.urlSlug}/`} />
))}
<link rel="alternate" hreflang="x-default"
href={`https://yoursite.com/en/articles/${currentSlug}/`} />
这样簇里每个页面看到的列表相同,且都链到所有成员(含自己)。
第 3 步:URL 格式完全一致
定一个 canonical URL 形式:https、host 带不带 www、带不带 trailing slash。一致到底:
# bash 跨页面检查
for url in "https://yoursite.com/en/articles/foo/" "https://yoursite.com/zh/articles/foo/"; do
curl -s "$url" | grep -oE 'hreflang="[^"]+" href="[^"]+"'
done
确认每个 hreflang URL 正好是那个语言版本的 canonical。
第 4 步:语言码统一
ISO 639-1 两字母(en、zh),或扩展(en-US、zh-CN)。选一种风格永不混用。中文:要区分字体用 zh-Hans(简)和 zh-Hant(繁);否则就 zh。
第 5 步:修 canonical
每个 hreflang 簇成员必须 rel=canonical 到自己,不是另一种语言。跨语言 canonical 是经典 bug,会打破 hreflang:
<!-- 错: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/">
第 6 步:CI 校验簇完整性
// scripts/check-hreflang.mjs
import fs from 'node:fs';
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);
byKey[fm.translationKey] ??= [];
byKey[fm.translationKey].push({ file: f, lang: fm.lang });
}
for (const [key, members] of Object.entries(byKey)) {
if (members.length < 2) continue; // 单语跳过
const langs = members.map(m => m.lang);
for (const m of members) {
if (!langs.includes(m.lang)) errors.push(`Missing self: ${m.file}`);
}
}
if (errors.length) process.exit(1);
第 7 步:重提,观察
Search Console → International Targeting(legacy 报告)和 Pages 报告。重抓 1-2 周内 “No return tags” 警告应消失。
哪些情况可能不是你操作错了
翻译团队手动只发一种语言、另一种延后,hreflang 在缺口期会警告。这是预期的;两边都有后警告自动消。
容易误判的情况
仅当作 canonical 问题。两个会互相影响:canonical 错就让 hreflang 失效。永远先修 canonical 指自己,再修 hreflang。
预防建议
- 用单个共享 layout helper 输出 hreflang;不要每页手写。
- CI 校验:同 translationKey 的文件跨语言齐全。
- 整站统一一种 URL 形式(trailing slash、host、protocol)。
- 每个语言版本 canonical 指自己。
- 每季度用 Screaming Frog 或自定义脚本审 hreflang。
FAQ
- 需要
x-default吗? 建议有但非必需。带语言选择器的全球落地页有用。 - Hreflang 能指向不同域名吗? 可以——
hreflang="ja"可指向yoursite.jp。return-tag 规则同样适用。