首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >多语言 WordPress 站:URL 结构和 hreflang 怎么配,才能让搜索引擎分清语言版本

多语言 WordPress 站:URL 结构和 hreflang 怎么配,才能让搜索引擎分清语言版本

原创
作者头像
用户10345225
发布于 2026-11-03 12:36:01
发布于 2026-11-03 12:36:01
370
举报

站点做成 /en/、/de/、/ja/ 三份之后,最常见的麻烦不是「没收录」,而是收错了:德语关键词搜出来的是英文页,Search Console 里一批 /de/ 页面写着「重复网页,Google 选择的规范网页与用户指定的不同」。

抓一下这些页面的源码,问题通常堆在一起:canonical 全指到英文版、hreflang 只在英文页上写了一份、德语页的地址还是 ?lang=de。任意一条成立,hreflang 基本就白写了。

下面按能落地的顺序走:先定 URL 结构,再做前缀路由,然后输出 hreflang,最后用几条命令验。

URL 结构先定下来,后面的全跟着它走

做法

地址长这样

适合什么情况

要提前想清楚

子目录

example.com/de/about/

一套 WordPress、一套主题和插件,绝大多数站点

前缀规则要统一;站内链接和 canonical 都得带前缀

子域名

de.example.com/about/

不同语言由不同团队或不同服务器维护

两套环境各自能跑;cookie 和 CDN 缓存按域分开

国家域名

example.de/about/

有本地实体、本地支付物流,按国家分别运营

每个域名单独解析、证书、备案,维护量按语言×国家翻

查询参数

example.com/about/?lang=de

不建议

同一页面多个地址,容易和站内搜索、广告参数混在一起,缓存键也要额外处理

多数情况选子目录:一个后台、一份内容、插件只装一遍。国家域名留给真有本地化运营需求的项目,别因为「看起来更专业」就上。

默认语言要不要带前缀

两种都能用,但必须二选一并坚持:全带(/en/about/、/de/about/),或者默认不带(/about/、/de/about/)。

选了「默认不带」,就要保证 /en/about/ 单向 301 到 /about/(不要互跳),而且 hreflang 里 en 那一份填的是 /about/,不是 /en/about/。

给语言前缀加路由

WordPress 默认不认 /de/about/ 这种地址。写一段 mu-plugin,放到 wp-content/mu-plugins/lang-prefix.php,自动生效,不用走插件启用流程:

代码语言:php
复制
<?php
/**
 * Plugin Name: Lang Prefix Router
 * Description: 语言前缀路由 + 互指 hreflang 的最小实现。
 */

function lpr_langs(): array {
    return ['en', 'de', 'ja'];                              // URL 里用的短码
}

function lpr_hreflang_map(): array {
    return ['en' => 'en', 'de' => 'de', 'ja' => 'ja'];      // 短码 => BCP 47
}

add_action('init', function () {
    $alt = implode('|', array_map('preg_quote', lpr_langs()));
    add_rewrite_tag('%lpr_lang%', '(' . $alt . ')');
    add_rewrite_rule(
        '^(' . $alt . ')/(.+?)/?$',
        'index.php?lpr_lang=$matches[1]&pagename=$matches[2]',
        'top'
    );
});

function lpr_current_lang(): string {
    $lang = get_query_var('lpr_lang');
    return ($lang && in_array($lang, lpr_langs(), true)) ? $lang : lpr_langs()[0];
}

/* 给任意站内 URL 换语言前缀 */
function lpr_url(string $url, string $lang): string {
    $home = untrailingslashit(get_option('home'));
    if (0 !== strpos($url, $home)) {
        return $url;                                        // 站外链接不动
    }
    $alt  = implode('|', array_map('preg_quote', lpr_langs()));
    $path = substr($url, strlen($home));
    $path = preg_replace('#^/(' . $alt . ')(?=/|$)#', '', $path);
    if ('' === $path) {
        $path = '/';
    }
    return $home . '/' . $lang . $path;
}

规则里的 pagename= 只覆盖页面。文章换成 name=,分类换成 category_name=,自定义文章类型用它自己的查询变量名,各加一条同样带 'top' 的规则。启用后去「设置 → 固定链接」点一次保存,让重写规则重建。

顺带把尾斜杠定死:如果访问 /de/about 会 301 到 /de/about/,那 hreflang 里写的就必须是带斜杠的最终地址。

站内链接和 canonical 要跟着语言走

不要在 home_url 上做全局过滤。后台菜单、REST 返回的 URL、邮件里的链接会被一起改,还容易递归把站点跑挂。在输出层处理:

代码语言:php
复制
/* canonical 自指:/de/about/ 的 canonical 就是 /de/about/ */
add_filter('get_canonical_url', function ($canonical, $post) {
    return lpr_url($canonical, lpr_current_lang());
}, 10, 2);

菜单项和正文链接用 lpr_url(get_permalink($id), lpr_current_lang()) 包一层,或者在模板里统一把 the_permalink() 的输出过一遍。检查办法很土但有效:切到德语版,随便点开几个页面,看地址栏里还有没有掉回无前缀的链接。

hreflang 要满足什么才算有效

  • 每组里包含自己(自指)
  • 互相指:A 指了 B,B 必须指回 A
  • 有 x-default,指向语言选择页或默认语言版本
  • 值用 BCP 47:简体通用写 zh-Hans,繁体写 zh-Hant,要区分地区才用 en-GB、pt-BR。en-EU 这类不存在;zh-CN 是国家码,不表示「简体」
  • href 必须是返回 200 的最终地址,不能是 301 中转,也不能带广告参数

翻译关系用 post meta 存一个组 ID(_lpr_group),每种语言一条内容,共用同一个组 ID:

代码语言:php
复制
function lpr_group_urls(int $post_id): array {
    $group = get_post_meta($post_id, '_lpr_group', true);
    if (!$group) {
        return [];
    }
    $q = new WP_Query([
        'post_type'      => 'any',
        'post_status'    => 'publish',
        'posts_per_page' => 20,
        'no_found_rows'  => true,
        'meta_query'     => [['key' => '_lpr_group', 'value' => $group]],
    ]);
    $out = [];
    foreach ($q->posts as $p) {
        $lang = get_post_meta($p->ID, '_lpr_lang', true);
        if ($lang && in_array($lang, lpr_langs(), true)) {
            $out[$lang] = lpr_url(get_permalink($p), $lang);
        }
    }
    return $out;
}

add_action('wp_head', function () {
    if (!is_singular()) {
        return;
    }
    $urls = lpr_group_urls(get_queried_object_id());
    if (count($urls) < 2) {
        return;                       // 只有一个语言版本时不要输出,半套比没有更糟
    }
    $map = lpr_hreflang_map();
    $def = lpr_langs()[0];
    foreach ($urls as $lang => $url) {
        printf(
            '<link rel="alternate" hreflang="%s" href="%s" />' . PHP_EOL,
            esc_attr($map[$lang] ?? $lang),
            esc_url($url)
        );
    }
    if (isset($urls[$def])) {
        printf(
            '<link rel="alternate" hreflang="x-default" href="%s" />' . PHP_EOL,
            esc_url($urls[$def])
        );
    }
}, 5);

如果你的翻译关系存在别的地方(分类法、多站点、单独一张对照表),只换 lpr_group_urls() 里取组和取语言的那几行,输出部分不用动。

分页和归档页同样要各自成组:/de/blog/page/2/ 这一组就写三种语言的第 2 页,不要全部指到第 1 页,也不要让第 2 页的 canonical 指到第 1 页。站内搜索结果页直接 noindex,不要放进 hreflang。

sitemap 里那份别和 HTML 打架

hreflang 写在 HTML 里或写在 sitemap 里都行,选一个当主。写在 sitemap 时是这种形式:

代码语言:xml
复制
<url>
  <loc>https://example.com/de/about/</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about/"/>
  <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/about/"/>
  <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about/"/>
</url>

两边都写、但地址对不上(一边带斜杠一边不带,一边 http 一边 https),是最难查的一类问题。

上线前用几条命令验一遍

代码语言:bash
复制
for l in en de ja; do
  echo "== $l"
  curl -sL "https://example.com/$l/about/" \
    | grep -Eo '<link rel="(canonical|alternate)"[^>]*>' | sort
done

要看到的结果:三次输出的 alternate 集合完全相同,canonical 各自指向自己那一版。再把里面每个 href 挨个抓一次,确认都是 200:

代码语言:bash
复制
curl -s -o /dev/null -w '%{http_code} %{url_effective}\n' "https://example.com/de/about/"

最后看日志:Googlebot 有没有真的抓过三种语言的地址。只抓英文版,多半是站内链接还停在无前缀形态,或者 robots.txt 把 /de/ 挡住了。

这套路由和 hreflang 输出我们自己在维护,也打包成了免费插件 AumLang,涵盖语言前缀、hreflang 和 WooCommerce 商品页翻译:https://aumcreate.com/plugins/aumlang?utm_source=tencent&utm_medium=content&utm_campaign=tencent-multilingual-url-hreflang-patterns。上面的代码可以直接拿去用;如果改用插件,记得先把自己那套撤掉,别让两套同时往页头里输出 hreflang。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • URL 结构先定下来,后面的全跟着它走
    • 默认语言要不要带前缀
  • 给语言前缀加路由
  • 站内链接和 canonical 要跟着语言走
  • hreflang 要满足什么才算有效
  • sitemap 里那份别和 HTML 打架
  • 上线前用几条命令验一遍
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档