hreflang 常见失效原因:缺 self-reference 和 x-default 指错
hreflang 出问题,先别急着改内容。先查两件事:当前页有没有指向自己的 hreflang;x-default 指向的 URL 是不是真正的默认页或语言选择页。这两个错误会让整组 hreflang 被忽略或选错页面。
1. self-reference 是什么
每个语言版本都必须包含指向自己的 hreflang。英文页要列英文页自己,中文页要列中文页自己。缺了 self-reference,搜索引擎可能不把这组链接当成完整集群。
错误示例:英文页只列中文和日文,不列英文。
<link rel='alternate' hreflang='zh-CN' href='https://example.com/zh/post/' />
<link rel='alternate' hreflang='ja' href='https://example.com/ja/post/' />
正确示例:
<link rel='alternate' hreflang='en-US' href='https://example.com/en/post/' />
<link rel='alternate' hreflang='zh-CN' href='https://example.com/zh/post/' />
<link rel='alternate' hreflang='ja' href='https://example.com/ja/post/' />
<link rel='alternate' hreflang='x-default' href='https://example.com/' />
注意:x-default 不要随手写成当前英文页,除非它真的是默认选择页。
2. x-default 指错的三种情况
- 指到 404 或重定向链:爬虫跟过去拿不到 200,整组信号变弱。
- 指到某个语言版本,但那个版本不是默认页,也不是语言选择页。
- 每个语言页都把自己的 URL 写成 x-default,导致同一组 hreflang 里出现多个不一致的 x-default。
正确做法:同一组 hreflang 里 x-default 只出现一次,并且所有语言页都指向同一个默认 URL。没有可靠的默认 URL 时,宁可不输出 x-default,也不要乱指。
3. 用 Python 脚本查当前页
保存为 check_hreflang.py,传入一个或多个 URL。脚本只检查当前页,不递归。
import sys
import urllib.request
from html.parser import HTMLParser
from urllib.parse import urljoin
class HreflangParser(HTMLParser):
def __init__(self):
super().__init__()
self.links = []
def handle_starttag(self, tag, attrs):
if tag.lower() != 'link':
return
d = dict(attrs)
if d.get('rel', '').lower() == 'alternate' and d.get('hreflang'):
self.links.append((d['hreflang'].lower(), d.get('href', '')))
def fetch(url):
req = urllib.request.Request(url, headers={'User-Agent': 'hreflang-check/1.0'})
with urllib.request.urlopen(req, timeout=15) as r:
return r.geturl(), r.read().decode('utf-8', 'replace')
def check(url):
final, html = fetch(url)
p = HreflangParser()
p.feed(html)
links = [(code, urljoin(final, href)) for code, href in p.links]
codes = [code for code, _ in links]
norm = lambda u: u.rstrip('/')
print('URL:', final)
for code, href in links:
print(' ', code, href)
if not links:
print('FAIL: 没有 hreflang')
return
if any(norm(href) == norm(final) for _, href in links):
print('OK: self-reference')
else:
print('FAIL: 缺 self-reference')
if 'x-default' not in codes:
print('FAIL: 缺 x-default')
elif codes.count('x-default') > 1:
print('FAIL: x-default 重复')
else:
print('OK: 有 x-default')
if __name__ == '__main__':
for u in sys.argv[1:]:
check(u)
运行:
python3 check_hreflang.py https://example.com/en/post/
4. WordPress 里输出带 self-reference 的 hreflang
在 wp_head 输出。当前页 URL 一定要进 $urls,这就是 self-reference。x-default 没有可靠来源时不输出。
add_action('wp_head', function () {
if (!is_singular()) {
return;
}
$post_id = get_queried_object_id();
$current = get_permalink($post_id);
if (!$current) {
return;
}
$current_lang = get_post_meta($post_id, '_aum_lang', true);
if (!$current_lang) {
$current_lang = 'zh-CN';
}
$urls = [];
$urls[$current_lang] = $current;
foreach (['zh-CN', 'en-US', 'ja'] as $code) {
if ($code === $current_lang) {
continue;
}
$url = get_post_meta($post_id, '_aum_translation_url_' . $code, true);
if ($url) {
$urls[$code] = $url;
}
}
$x_default = get_post_meta($post_id, '_aum_translation_url_x-default', true);
if (!$x_default) {
$default_lang = 'zh-CN';
if (isset($urls[$default_lang])) {
$x_default = $urls[$default_lang];
}
}
foreach ($urls as $code => $url) {
printf(
"<link rel='alternate' hreflang='%s' href='%s' />" . PHP_EOL,
esc_attr($code),
esc_url($url)
);
}
if ($x_default) {
printf(
"<link rel='alternate' hreflang='x-default' href='%s' />" . PHP_EOL,
esc_url($x_default)
);
}
});
这里有两个点:
$urls[$current_lang] = $current保证当前页包含自己。$x_default只在有可靠来源时输出;没有就跳过,避免把 x-default 指错。
5. 修复后的验收清单
- 每个语言页都包含指向自己的 hreflang。
- 同一组页面里的 hreflang 集合一致,不要有的页多一个、有的页少一个。
- x-default 只有一个,且所有语言页指向同一个 URL。
- 所有 hreflang URL 都是绝对地址,返回 200,不经过重定向链。
- self-reference 的 URL 要和 canonical 一致。
- 不要用 JavaScript 动态插入 hreflang,爬虫可能看不到。
- 改完用上面的脚本逐页跑一遍,不要只查首页。
如果不想手写这套输出逻辑,AumLang 是免费插件,做 AI 翻译、hreflang 和 WooCommerce 商品页翻译。插件页:aumcreate.com/plugins/aum…