SEO//12 読了時間

hreflang タグ:多言語SEOのための完全実装ガイド

Eray Gündoğmuş
共有

hreflang タグ:多言語SEOのための完全実装ガイド

複数の言語でウェブサイトを運営している場合や、異なる国のユーザーをターゲットにしている場合、hreflang タグは実装できる最も重要な技術的SEOシグナルです。正しく設定すれば、Googleは各ユーザーに正しい言語バージョンを提供します。誤って設定すると、重複コンテンツのペナルティ、自サイト内でのカニバリゼーション、そして積極的にサービスを提供しようとしている地域でのランキング低下に直面することになります。

このガイドでは、hreflang タグが実際に何をするのか、3つの実装方法、実際に遭遇する一般的なパターン、モダンなJavaScriptスタック向けのフレームワーク固有のコードスニペット、実装を静かに壊すミス、そしてリリース前にすべてを検証するために必要なツールについて、全体像を解説します。


TL;DR / 重要なポイント

  • hreflang は、特定のオーディエンスに対してどの言語またはリージョンバリアントのページを提供するかをGoogleに伝えるHTML属性です。Bingはhreflangを使用しません — Content-Language HTTPヘッダーやジオロケーションデータなど、他のシグナルを使用します。
  • hreflang セット内の各ページは、自分自身を指す自己参照タグと、他のすべてのバリアントを指すタグを含める必要があります。
  • hreflang の関係は双方向でなければなりません:ページAがページBをバリアントとして宣言した場合、ページBもページAをバリアントとして宣言する必要があります。一方向の宣言は無視されます。
  • x-default 値はフォールバック指定子であり、言語コードではありません。言語セレクター、リダイレクトゲートウェイ、またはマッチしないロケールのキャッチオールとして機能するページに使用します。
  • hreflang は3つの場所に実装できます:<head> 内のHTML <link> タグ、HTTPレスポンスヘッダー、またはXMLサイトマップ。3つともGoogleに対して同等に有効です。

hreflang タグとは何ですか?

hreflang タグは、特定の言語を話すユーザーや特定の国にいるユーザーを対象としたページのバージョンを検索エンジンに伝えるHTML注釈です。この属性は2011年にGoogleによって導入され、Google Search Central開発者ドキュメントの「ページのローカライズ版をGoogleに伝える」という名称で文書化されています。

hreflang が解決するコアの問題は曖昧性の解消です。ウェブサイトが /en/ で英語、/de/ でドイツ語、そして /de-at/ でオーストリア向けのドイツ語で存在するとします。hreflang がなければ、検索エンジンのクローラーは実質的に似たコンテンツを持つ3つのページを見て、どれをどのオーディエンスに対してランク付けするかを推測しなければなりません。多くの場合、推測は外れます。hreflang を使えば、推測を排除して関係を明示的に宣言できます。

GoogleがどのようにHreflangを使用するか

Googleはhreflang注釈に遭遇すると、それを使用してページを等価セットにグループ化します。セット内で、Googleはユーザーのインターフェース言語、そして程度は低いですが場所に基づいて、各検索クエリに対して最も適切なバリアントを選択します。hreflang はランキングを保証しません — ページがすでにランキングシグナルを持っている場合に、特定のオーディエンスに対してどのページを優先するかをGoogleに伝えるだけです。

hreflang は重複コンテンツも解決します。Googleが同一または類似のコンテンツを持つ2つのURLと、それらを接続する有効なhreflangリンクを見た場合、偶発的な重複ではなく意図的な地域バリアントとして扱います。

Google vs Bing:重要な違い

hreflang はGoogle(およびYandex)の機能です。Bingは明示的にhreflang タグを使用しません。Bing Webmaster Guidelinesによると、Bingはページのターゲット言語と地域を決定するために次のシグナルを使用します:

  • Content-Language HTTPレスポンスヘッダー
  • <html> 要素の lang 属性
  • サーバーの地理的な場所
  • 国コードトップレベルドメイン(ccTLD)(該当する場合)
  • ページのコンテンツ自体

Bingが国際オーディエンスにとって重要なトラフィックソースである場合、hreflang のセットアップとは独立してこれらのシグナルを実装する必要があります。適切に構築された多言語サイトは通常、両方を実装します:GoogleにはhreflangとBingには<html>lang属性とContent-Languageヘッダーを使用します。2つのアプローチは競合しません。


構文と配置

言語コードと地域コードの形式

hreflang の値はIETF BCP 47で定義された形式に従います。実際には:

  • 言語のみ:endefrjazh-Hanszh-Hant
  • 言語プラス地域:en-USen-GBde-ATfr-CApt-BR
  • 特殊な値:x-default

言語コードはISO 639-1の2文字コードです。地域コードはISO 3166-1 alpha-2の2文字国コードです。区切り文字はアンダースコアではなくハイフンです。よくある間違いは、en-US(ハイフン)の代わりにen_US(アンダースコア)と書くことです — Googleはアンダースコアのバリアントを完全に無視します。

これは最も広く使われている方法です。hreflangセット内の各ページの<head><link>要素を配置します。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Product Page</title>

  <!-- 自己参照タグ(必須) -->
  <link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

  <!-- 他の言語バリアント -->
  <link rel="alternate" hreflang="de" href="https://example.com/de/product/" />
  <link rel="alternate" hreflang="fr" href="https://example.com/fr/product/" />
  <link rel="alternate" hreflang="de-AT" href="https://example.com/de-at/product/" />

  <!-- マッチしないロケールのフォールバック -->
  <link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
</head>
<body>
  <!-- ページコンテンツ -->
</body>
</html>

このページのすべての言語バリアントに、まったく同じ<link>タグセットを含める必要があります。/de/product/ のドイツ語ページも、/en/product/ を指すタグを含む5つの<link>要素すべてを持つ必要があります。

方法2:HTTPレスポンスヘッダー

非HTMLリソース(最も一般的なケースはPDF)では、HTMLタグを埋め込むことができません。代わりに、ウェブサーバーがHTTP Link レスポンスヘッダーの一部としてhreflangを発行します。

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Link: <https://example.com/en/product/>; rel="alternate"; hreflang="en",
      <https://example.com/de/product/>; rel="alternate"; hreflang="de",
      <https://example.com/fr/product/>; rel="alternate"; hreflang="fr",
      <https://example.com/product/>; rel="alternate"; hreflang="x-default"

Nginx設定では次のようになります:

location /en/product/ {
    add_header Link '<https://example.com/en/product/>; rel="alternate"; hreflang="en", <https://example.com/de/product/>; rel="alternate"; hreflang="de", <https://example.com/fr/product/>; rel="alternate"; hreflang="fr"';
}

方法3:XMLサイトマップ

数千のURLを持つ大規模サイトでは、個々のページすべてにhreflangタグを維持することは運用上コストがかかります。XMLサイトマップは、各<url>ブロック内のxhtml:link要素を通じて一元化された代替手段を提供します。

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">

  <!-- 英語バリアント -->
  <url>
    <loc>https://example.com/en/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

  <!-- ドイツ語バリアント — xhtml:link要素の同じセット -->
  <url>
    <loc>https://example.com/de/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

  <!-- フランス語バリアント — xhtml:link要素の同じセット -->
  <url>
    <loc>https://example.com/fr/product/</loc>
    <xhtml:link rel="alternate" hreflang="en"       href="https://example.com/en/product/" />
    <xhtml:link rel="alternate" hreflang="de"       href="https://example.com/de/product/" />
    <xhtml:link rel="alternate" hreflang="fr"       href="https://example.com/fr/product/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/product/" />
  </url>

</urlset>

一般的なhreflangパターン

同じ言語、異なる地域

同じ言語で異なる国にとって意味のある異なるコンテンツがある場合(価格の違い、法的文書の違い、製品の可用性の違い)は、言語+地域タグを使用します。

<!-- アメリカ英語 -->
<link rel="alternate" hreflang="en-US" href="https://example.com/us/checkout/" />

<!-- イギリス英語 -->
<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/checkout/" />

<!-- オーストラリア英語 -->
<link rel="alternate" hreflang="en-AU" href="https://example.com/au/checkout/" />

<!-- 他のすべての英語話者へのフォールバック -->
<link rel="alternate" hreflang="en" href="https://example.com/en/checkout/" />

x-defaultタグ

x-default は、言語が宣言されたバリアントのいずれにも一致しないユーザーにGoogleが表示すべきページを指定します。

<!-- 言語別バリアント -->
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/" />

<!-- x-default:ユーザーの言語にバリアントが一致しない場合に表示 -->
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

x-default は言語コードではありません。英語の同義語として使用しないでください。

自己参照要件

hreflangセット内のすべてのページは、自分自身を指すタグを含める必要があります。これはオプションではありません。Googleのドキュメントには明示的に記載されています。

<!-- /de/ ページでは、自己参照は hreflang="de" タグです -->
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

リターンリンク要件(双方向性)

ページAがページBをオルタネートとして宣言した場合、ページBもページAをオルタネートとして宣言する必要があります。これはhreflang実装で最も一般的なサイレントな失敗です。


人気フレームワークでの実装

Next.js(App Router)

Next.js App Routerでは、generateMetadata 関数で alternates プロパティを使用します。

// app/[locale]/product/[slug]/page.tsx
import type { Metadata } from 'next';

type Props = {
  params: { locale: string; slug: string };
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { locale, slug } = params;
  const baseUrl = 'https://example.com';

  return {
    alternates: {
      canonical: `${baseUrl}/${locale}/product/${slug}/`,
      languages: {
        'en': `${baseUrl}/en/product/${slug}/`,
        'de': `${baseUrl}/de/product/${slug}/`,
        'fr': `${baseUrl}/fr/product/${slug}/`,
        'x-default': `${baseUrl}/en/product/${slug}/`,
      },
    },
  };
}

Nuxt 3

Nuxtsの@nuxtjs/i18n モジュールは、モジュール設定で seo: true が設定されている場合、hreflang タグを自動的に生成します。手動制御には useHead コンポーザブルを使用します。

// composables/useHreflang.ts
export function useHreflang(slug: string) {
  const baseUrl = 'https://example.com';
  const locales = ['en', 'de', 'fr'];

  useHead({
    link: [
      ...locales.map((locale) => ({
        rel: 'alternate' as const,
        hreflang: locale,
        href: `${baseUrl}/${locale}/${slug}/`,
      })),
      {
        rel: 'alternate' as const,
        hreflang: 'x-default',
        href: `${baseUrl}/en/${slug}/`,
      },
    ],
  });
}

Astro

AstroのgetStaticPathsは、各ページの完全なバリアントセットを構築して共有レイアウトに渡すことを簡単にします。

---
// src/layouts/BaseLayout.astro
interface Props {
  hreflangLinks: Array<{ hreflang: string; href: string }>;
}
const { hreflangLinks } = Astro.props;
---
<html>
  <head>
    {hreflangLinks.map((link) => (
      <link rel="alternate" hreflang={link.hreflang} href={link.href} />
    ))}
  </head>
  <body>
    <slot />
  </body>
</html>
---
// src/pages/[locale]/[slug].astro
export async function getStaticPaths() {
  const baseUrl = 'https://example.com';
  const locales = ['en', 'de', 'fr'];
  const slugs = ['about', 'pricing', 'contact'];

  return locales.flatMap((locale) =>
    slugs.map((slug) => ({
      params: { locale, slug },
      props: {
        hreflangLinks: [
          ...locales.map((l) => ({
            hreflang: l,
            href: `${baseUrl}/${l}/${slug}/`,
          })),
          { hreflang: 'x-default', href: `${baseUrl}/en/${slug}/` },
        ],
      },
    }))
  );
}
const { hreflangLinks } = Astro.props;
---
<BaseLayout hreflangLinks={hreflangLinks}>
  <!-- ページコンテンツ -->
</BaseLayout>

TanStack Start

TanStack StartはcreateFileRouteを使ったファイルベースのルーティングを使用します。ルートファイルのheadエクスポートを使用してhreflangタグを注入します。

// routes/$locale.product.$slug.tsx
import { createFileRoute } from '@tanstack/start';

const locales = ['en', 'de', 'fr'] as const;
const baseUrl = 'https://example.com';

export const Route = createFileRoute('/$locale/product/$slug')({
  head: ({ params }) => {
    const { locale, slug } = params;

    const hreflangLinks = [
      ...locales.map((l) => ({
        tag: 'link' as const,
        attrs: {
          rel: 'alternate',
          hreflang: l,
          href: `${baseUrl}/${l}/product/${slug}/`,
        },
      })),
      {
        tag: 'link' as const,
        attrs: {
          rel: 'alternate',
          hreflang: 'x-default',
          href: `${baseUrl}/en/product/${slug}/`,
        },
      },
    ];

    return { links: hreflangLinks };
  },
  component: ProductPage,
});

function ProductPage() {
  const { locale, slug } = Route.useParams();
  return <div>{/* ページコンテンツ */}</div>;
}

よくある間違い

自己参照の欠如

すべてのページは自分自身を宣言する必要があります。これを忘れることが、Google Search Consoleがhreflangエラーを報告する最大の原因です。

<!-- 誤り:/de/ ページが自分自身を参照していない -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

<!-- 正しい:/de/ ページが自分自身の自己参照を含む -->
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about/" />

リターンリンクの欠如

新しいロケールを追加する場合、セット内の既存のすべてのページを更新して新しいバリアントへのリンクを含める必要があります。

誤った言語コード

一般的なエラー:

  • en-USの代わりにen_US(アンダースコア対ハイフン)
  • zh-Hans(簡体字)またはzh-Hant(繁体字)の意味でzhを使用
  • pt-PT(ポルトガル)とpt-BR(ブラジル)を区別する必要があるときにptを使用
  • 2文字のISO 639-1コード(zhde)の代わりに3文字のISO 639-2コード(zhodeu)を使用

hreflangと誤ったcanonicalタグの混在

canonical タグとhreflang自己参照は同じURLを指す必要があります。

<!-- 誤り:canonicalとhreflang自己参照が一致しない -->
<link rel="canonical" href="https://example.com/product/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

<!-- 正しい:canonicalと自己参照が同じURLを指す -->
<link rel="canonical" href="https://example.com/en/product/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/product/" />

検証ツール

Google Search Console

Search ConsoleのCoverageレポートはhreflangエラーを直接表示します。一般的なエラータイプ:

  • Return tag missing:Googleがhreflangリンクを見つけましたが、ターゲットページがリンクバックしていません。
  • No hreflang tag:ページがオルタネートとして参照されていますが、hreflangタグを含んでいません。

hreflangチェッカーツール

  • hreflang.org Tag Checker:URLを貼り付けて、そのページで見つかったすべてのhreflang注釈とリターンリンクの存在を確認します。
  • Aleyda SolisのHreflang Tags Testing Tool:個々のURLのスポットチェックに役立つ無料ツールです。
  • MerkleのHreflang Tag Generator:構造化された入力から構文的に正しいHTML、HTTPヘッダー、またはサイトマップマークアップを生成します。

Screaming Frog SEO Spider

大規模な監査では、Screaming FrogのSEO Spiderがサイト全体をクロールし、見つかったすべてのhreflang注釈、ターゲットURL、双方向性要件が満たされているかどうかを示すレポートを作成します。


FAQ

hreflangはランキングに直接影響しますか?

いいえ。hreflangは重複コンテンツの問題を防ぎ、正しいページが正しいオーディエンスに対して競争することを保証します。これにより、クリック率とエンゲージメントシグナルが間接的に向上する可能性があります。

1つの言語だけど複数の国バージョンがあるサイトでhreflangを使用できますか?

はい。コンテンツが本当に異なる(価格、法的コピー、製品の可用性)場合、en-USen-GBen-AUタグを使用することは正しいです。

Googleがhreflangの変更を処理するのにどのくらいかかりますか?

ほとんどのサイトでは、変更が完全に反映されるまでに数日から数週間かかります。Search Consoleを通じて更新されたXMLサイトマップを送信すると、プロセスが加速します。

hreflangはすべてのページに実装すべきですか、それともキーページだけですか?

翻訳されたバリアントを持つすべてのページに実装すべきです。部分的な実装は予測不可能な動作を引き起こす不整合を生じさせます。

hreflangエラーがあって何もしなかった場合はどうなりますか?

Googleは誤ったhreflang注釈を存在しないものとして扱い、独自のシグナルにフォールバックします。通常、誤った地域バリアントが検索結果に表示されます。


まとめ

hreflang タグは国際サイトにとって技術的に最も要求の高いSEO要件の1つです。個々のタグが複雑なのではなく、実装がすべてのページ、すべてのバリアント、選択したすべての方法で一貫している必要があるからです。

実用的なチェックリスト:ハイフンを使ったBCP 47言語コード、常に自己参照、常に双方向リンク、canonical タグをhreflang自己参照と一致させる、リリース前にScreaming FrogとSearch Consoleで検証する。

新しいプロジェクトでは、i18nルーティングの一部としてhreflang出力を自動的に生成するフレームワークまたはプラットフォームを選択し、手動で維持するのではなく出力を検証することが最小抵抗の道です。既存のサイトでは、XMLサイトマップの実装が通常最も摩擦の少ない改善方法です。


参考資料

Comments

Loading comments...