Amazonで商品を見る セール会場へ

WordPress│多言語サイトのhreflangタグを動的に出力する方法

hreflangは、同じ内容の言語別ページを対応付けるために使います。

URLのパスが同じという理由だけで翻訳ページを推測せず、確認したURLの組を登録します。

旧版の毎アクセス時のHEAD確認と、ページごとに区別されないJSON-LDは撤去しました。

目次

著者

WEB制作をしているデジタルノマド
WordPressのカスタマイズが好きで、色々と自作しています。

WordPressのカスタマイズに困ったらご相談ください!

設置前に対応表を作る

日本語・英語それぞれの正規URL、公開状態、インデックス可能な状態、翻訳内容を確認します。

200を返すだけでは翻訳の存在やcanonical・noindexの状態は判断できません。

リダイレクト先や別内容のページは登録しません。

確認済みURLだけを出力する

子テーマfunctions.phpなどへ追加し、groupsの例示ドメインを実際の対応URLへ置き換えます。

既存PHP内では開始タグを追加しません。

日本語側と英語側の両方へ同じ組を登録してください。

多言語プラグインがすでに出力している場合は重複追加しません。

function waza_hreflang_group_html($current, $groups) {
    $matches = array_values(array_filter($groups, function ($group) use ($current) {
        return is_array($group) && in_array($current, $group, true);
    }));
    // 同じURLが複数の組にある場合は、曖昧な対応を出力しない。
    if (count($matches) !== 1) return '';
    foreach ($matches as $group) {
        $html = '';
        foreach ($group as $lang => $url) {
            if (!preg_match('/^[a-z]{2,3}(?:-[A-Za-z]{2}|-[0-9]{3})?$/D', $lang)) return '';
            if (!is_string($url) || wp_parse_url($url, PHP_URL_SCHEME) !== 'https' || !wp_parse_url($url, PHP_URL_HOST)) return '';
            $html .= '<link rel="alternate" hreflang="' . esc_attr($lang) . '" href="' . esc_url($url) . '">' . "\n";
        }
        return $html;
    }
    return '';
}
function waza_output_mapped_hreflang() {
    if (is_admin() || !is_singular() || is_preview() || is_feed() || is_404()) return;
    $post_id = get_queried_object_id();
    if (get_post_status($post_id) !== 'publish' || post_password_required($post_id)) return;
    // インデックス可能な翻訳済みページだけを、両サイトに同じ組で登録する。
    $groups = array(
        array('ja' => 'https://example.com/about/', 'en' => 'https://en.example.com/company/'),
    );
    echo waza_hreflang_group_html(get_permalink($post_id), $groups);
}
add_action('wp_head', 'waza_output_mapped_hreflang', 5);

この例は投稿・固定ページなどの公開済み個別ページに限定し、一覧・検索・404・プレビュー・フィードを対象にしません。

対応表にないページでは何も出力しません。

投稿パスが言語ごとに異なっていても、URLを明示すれば対応付けられます。

対応表の重複と出力対象を切り分ける

状況この例の出力
公開済みの個別ページが1つの組に存在その組の自分自身と翻訳先
対応表にないURL出力しない
同じURLが複数の組に存在出力しない。重複した組を修正する
プレビュー・パスワード保護・検索結果出力しない

タグが出ない場合は、対応表内の各URLがHTTPS形式でホスト名を含むことと、各言語コードが掲載コードの正規表現に合うことも確認します。

一致した組に1件でもこの検査を通らない値があると、このコードはその組全体を出力しません。

現在のページとの照合はURL文字列の完全一致なので、末尾のスラッシュやクエリを含め、get_permalinkが返すURLと対応表をそろえます。

掲載コードの言語コード検査は、Googleが扱うすべての形式には対応していません。

たとえばGoogleが扱うzh-Hantはこの正規表現を通らず、逆にes-419は正規表現を通ってもGoogleでは未対応です。

形式を追加するときは、下記のGoogle公式資料で言語・地域・文字体系の指定を確認し、コードの検査条件と両言語の出力を検証してください。

旧処理は、同じURLを複数の組へ登録すると先頭の組だけを使用していました。

今回のコードは一致する組が1つであることを確認してから出力します。

タグが消えた場合は、対象URLが対応表にないか、複数の組に重複していないかを確認してください。

WordPressの実際のwp_headで、対応する2投稿には同じ2本のタグが出ること、未登録・プレビュー・パスワード保護・検索結果には出ないことを確認しました。

検証用URLはHTTPS形式へ置き換えており、実サイトへのHTTPS接続や翻訳内容を検証したものではありません。

noindex・canonical・相互リンクを確認

このコードはSEOプラグインごとのnoindex設定を自動判定しません。

noindexに変更したページは対応表から外します。

公開URLのHTMLソースで自分自身と翻訳先の両方があり、相手側にも戻りの指定があること、canonicalと矛盾しないことを確認します。

SEOプラグイン更新後も確認してください。

x-defaultは言語選択ページなどの目的に応じて設計するため、この例には含めません。

JSON-LDの翻訳関係を付けてもhreflangの代わりにはなりません。

地域コードや複数の対応グループを増やす場合は、1つのURLが複数組へ重複しないようにします。

戻し方と検証範囲

今回の関数とフック登録を取り除けば出力は止まります。

対応表の重複拒否とWordPressの出力条件は検証環境で確認しました。

実際の多言語サイトの相互タグ・canonical・noindexと、検索エンジンでの認識は未検証です。

対象サイトで両言語の公開HTMLを照合してください。

基準:Google公式の言語別ページの指定(2026年10月4日確認)。

Bogoを使う場合は標準のhreflangと重ねない

Bogo 3.9.3で関連付けた日本語・英語の公開投稿には、Bogo自身がwp_headから両言語のalternateタグを出します。

この対応表のコードも追加すると、同じ言語のタグを重複させるため、まず既存の出力を確認します。

今回の例をBogoと一緒に設置する手順ではありません。

2026年10月4日、隔離WordPressの別PHPリクエストで、関連付けた2投稿を指すjaとen-USのタグを確認しました。

URLのlangクエリも含めて照合します。

これはPHP出力の確認で、実HTTPS応答や検索エンジンでの認識を確認したものではありません。

Bogoの翻訳作成後の確認も参照し、翻訳先のcanonicalが元言語のURLを指していないか調べます。

  • URLをコピーしました!

実装を任せたい方へ

WordPressのカスタマイズ代行

ブロックの追加、テーマの改修、管理画面の調整など、記事で紹介している内容の実装を承ります。1時間 ¥8,000〜(税込)。

WAZAの有料記事のサブスクリプションも開始しました。

サービス

Service

WordPressサイトのカスタマイズのサービスに関心がありましたら、ぜひ詳細をご覧ください。

目次