戻る

多言語対応Next.jsアプリのSEOを最適化する方法

General Translation avatarGeneral Translation
guideseointernationalizationnextjsi18nroutingmetadata

多言語 SEO に特別な配慮が必要な理由

Next.js アプリに i18n を導入する多くの開発者は、UI を翻訳したところで完了だと考えてしまいます。 しかし、検索エンジンが各言語バージョンを発見してインデックスし、正しく関連付けできなければ、 その翻訳作業はすべて見えないままです。

適切に SEO 設定が行われていない多言語サイトには、次のような問題があります:

  • Google が 1 つの言語バージョンだけをインデックスし、他を無視してしまう
  • スペイン語で検索しているユーザーに英語ページが表示されてしまう
  • Google が /en/about/fr/about を同じページと見なすことで、重複コンテンツによるペナルティが発生する
  • 検索結果のスニペットに誤った言語が表示される

朗報: Next.js で多言語 SEO を正しく実装することは、決して難しくありません。 押さえるべきポイントは 5 つだけで、このガイドではそれらすべてを gt-next を使って解説します。


1. ロケールベースの URL ルーティング

多言語 SEO の基盤となるのは、言語ごとに別々の URL を用意することです。 検索エンジンは、各言語バージョンをそれぞれ独立してインデックスするために、個別にクロール可能な URL を必要とします。

つまり、必要なのは URL にロケールを含めること であり、Cookie やクエリパラメータ、Accept-Language の検出だけに頼ることではありません。

✅ generaltranslation.com/en/about
✅ generaltranslation.com/fr/about
✅ generaltranslation.com/es/about

❌ generaltranslation.com/about?lang=fr
❌ generaltranslation.com/about (ロケールがCookieに保存されている場合)

gt-next を使ったロケールルーティングの設定

まず、ページを [locale] という動的セグメント配下にネストします:

app/
└── [locale]/
    ├── layout.tsx
    ├── page.tsx
    └── about/
        └── page.tsx

次に、プロジェクトのルートディレクトリに middleware を作成します(Next.js 16 以上では proxy.ts、Next.js 15 以下では middleware.ts)。

import { createNextMiddleware } from 'gt-next/middleware';

export default createNextMiddleware();

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};

これにより、URL にロケールのプレフィックスが自動的に付きます。 デフォルトでは、デフォルトロケール(例:英語)にはプレフィックスが付かないため /about はクリーンなままですが、 スペイン語のユーザーには /es/about、フランス語のユーザーには /fr/about が表示されます。


2. HTML の lang 属性を設定する

<html> タグの lang 属性は、そのページがどの言語で書かれているかをブラウザと検索エンジンに伝えます。 アクセシビリティと SEO の両方において、最も簡単で効果の大きい対策のひとつです。

これがないとスクリーンリーダーは言語を推測する必要があり(多くの場合間違えます)、検索エンジンもそのページの言語判定に自信を持てなくなります。

gt-next は、ルートレイアウトでこれを簡単に行える useLocale フックを提供しています。

import { useLocale, GTProvider } from 'gt-next';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const locale = useLocale();

  return (
    <html lang={locale}>
      <body>
        <GTProvider>
          {children}
        </GTProvider>
      </body>
    </html>
  );
}

useLocale は BCP 47 形式のロケールコード(例: en-USarzh-Hans)を返します。


3. 正規 URL(Canonical URLs)

カノニカルタグは、ページの「主要な」バージョンがどの URL であるかを検索エンジンに伝えます。 多言語サイトでは、各言語版ページが自分自身の URL を正規 URL(カノニカル)として指定する必要があります。

<!-- /fr/about ページの場合 -->
<link rel="canonical" href="https://example.com/fr/about" />

これにより、検索エンジンがフランス語ページを英語ページの重複コンテンツとして扱うのを防げます。

Next.js では、metadata API を使ってカノニカル URL を設定します。 gt-next の getLocale と組み合わせて、各ロケールごとに正しいカノニカル URL を生成します。

import { getLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';

export async function generateMetadata() {
  const locale = await getLocale();

  return {
    alternates: {
      canonical: `${BASE_URL}/${locale}/about`,
    },
  };
}

export default function AboutPage() {
  return <h1>About Us</h1>;
}

プレフィックスのないデフォルトロケールについては、適宜調整してください。

import { getLocale, getDefaultLocale } from 'gt-next/server';

export async function generateMetadata() {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();
  const path = '/about';

  const prefix = locale === defaultLocale ? '' : `/${locale}`;

  return {
    alternates: {
      canonical: `${BASE_URL}${prefix}${path}`,
    },
  };
}

4. Hreflang タグ

Hreflang タグは、多言語 SEO において最も重要なシグナルです。 検索エンジンに対して「このページには他の言語版もあり、その URL はこれらです」と伝えます。

Hreflang がない場合、Google は検索結果にどの言語バージョンを表示すべきかを推測するしかなく、多くの場合その判断を誤ります。

<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/about" />
<link rel="alternate" hreflang="es" href="https://example.com/es/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />

x-default タグは、指定した言語のどれともユーザーの言語が一致しない場合に、どの URL を表示すべきかを検索エンジンに伝えます。

Next.js では、Metadata API の alternates.languages プロパティを使って hreflang を追加できます。 以下は、gt-next で動作する再利用可能なヘルパー関数です。

import { getLocale, getDefaultLocale } from 'gt-next/server';

const BASE_URL = 'https://example.com';
const SUPPORTED_LOCALES = ['en', 'fr', 'es'];

export async function getI18NMetadata(path: string) {
  const locale = await getLocale();
  const defaultLocale = getDefaultLocale();

  const getUrl = (loc: string) => {
    const prefix = loc === defaultLocale ? '' : `/${loc}`;
    return `${BASE_URL}${prefix}${path}`;
  };

  const languages: Record<string, string> = {};
  for (const loc of SUPPORTED_LOCALES) {
    languages[loc] = getUrl(loc);
  }
  languages['x-default'] = getUrl(defaultLocale);

  return {
    alternates: {
      canonical: getUrl(locale),
      languages,
    },
  };
}

次に、任意のページで使用します:

import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  return await getI18NMetadata('/about');
}

これにより、canonical タグとすべての hreflang タグが1回の呼び出しで生成されます。


5. 翻訳されたメタデータ

検索エンジンは、検索結果にページタイトルと説明文を表示します。 フランス語ページなのにこれらが英語のままだと、ユーザーにクリックされにくくなり、Google がその結果の掲載順位を下げる可能性があります。

gt-next's getGT 関数を使って、メタデータの文字列を翻訳します。

import { getGT } from 'gt-next/server';
import { getI18NMetadata } from '@/lib/i18n-metadata';

export async function generateMetadata() {
  const t = await getGT();
  const i18nMeta = await getI18NMetadata('/about');

  return {
    title: t('About Us'),
    description: t('Learn about our mission and team.'),
    ...i18nMeta,
  };
}

これにより、検索結果でタイトルと説明文が各言語にローカライズされるようになり、 英語以外の検索クエリからのクリック率が大幅に向上します。


6. 多言語サイトマップ

サイトマップは、すべてのページ(各言語バージョンを含む)を検索エンジンが検出できるようにするためのものです。 多言語サイトでは、サイトマップに hreflang アノテーションも含めるべきです。

Next.js は、sitemap.ts ファイルを使ったプログラムによるサイトマップをサポートしています。

import { MetadataRoute } from 'next';

const BASE_URL = 'https://example.com';
const LOCALES = ['en', 'fr', 'es'];
const DEFAULT_LOCALE = 'en';
const PAGES = ['/', '/about', '/blog', '/contact'];

export default function sitemap(): MetadataRoute.Sitemap {
  return PAGES.flatMap((path) => {
    const getUrl = (locale: string) => {
      const prefix = locale === DEFAULT_LOCALE ? '' : `/${locale}`;
      return `${BASE_URL}${prefix}${path === '/' ? '' : path}`;
    };

    const languages: Record<string, string> = {};
    for (const locale of LOCALES) {
      languages[locale] = getUrl(locale);
    }

    return LOCALES.map((locale) => ({
      url: getUrl(locale),
      lastModified: new Date(),
      alternates: { languages },
    }));
  });
}

これにより、ロケールごと・ページごとに1件のエントリを持つサイトマップが生成され、 各エントリには、すべての言語バージョンへのリンクを示す hreflang アノテーションが含まれます。


チェックリスト

ここまでの内容を手短にまとめます。

SEO 要件実装
Locale in URLcreateNextMiddleware()[locale] 動的セグメント
HTML lang attributeルートレイアウトでの useLocale()
Canonical URLsgetLocale() + Next.js metadata API の alternates.canonical
Hreflang tagsすべての対応ロケールを指定した Next.js metadata API の alternates.languages
Translated metadataページタイトルとディスクリプション用の getGT()
Multilingual sitemap各ロケールごとのエントリと hreflang alternates を持つ sitemap.ts

次のステップ