戻る

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

Team avatarTeam
guideseointernationalizationnextjsi18nroutingmetadata

多言語 SEO に特別な注意が必要な理由

Next.js アプリに i18n を導入する多くの開発者は、UI を翻訳した時点で完了だと考えがちです。 しかし、検索エンジンが各言語版を見つけられず、インデックスもできず、適切に関連付けられなければ、 せっかくの翻訳作業もユーザーの目には触れません。

SEO が適切に設定されていない多言語サイトでは、次のような問題が起こります。

  • Google が 1 つの言語版しかインデックスせず、ほかを無視してしまう
  • スペイン語で検索したユーザーに英語のページが表示される
  • Google が /en/about/fr/about を同じページと見なして、重複コンテンツのペナルティを受ける
  • 検索結果のスニペットに誤った言語が表示される

朗報です: Next.js で多言語 SEO を正しく設定するのは難しくありません。 押さえるべきポイントは 6 つあり、このガイドでは 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 (with locale in a cookie)

gt-next でロケールルーティングを設定する

まず、ページを [locale] の動的セグメント配下に配置します。

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

次に、プロジェクトのルートにミドルウェアを作成します (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が自動的に設定されます。 デフォルトでは、デフォルトのロケール (例: English) には接頭辞が付かないため、/about はそのままですが、 Spanish のユーザーには /es/about、French のユーザーには /fr/about が表示されます。


2. HTML lang 属性の設定

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

これがないと、スクリーンリーダーは言語を推測するしかなく (しかもよく間違えます) 、検索エンジンもその言語分類に十分な確信を持てなくなります。

gt-next には、ルートレイアウトでこれを簡単に実現できる useLocale Hook が用意されています。

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 タグは、ページの"正規"なURLがどれかを検索エンジンに伝えるためのものです。 多言語サイトでは、各言語版ページが正規URLとして自分自身を指定する必要があります。

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

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

Next.js では、metadata API を使って canonical を設定します。 これを gt-next の getLocale と組み合わせると、各ロケールに対応した正しい canonical を生成できます:

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');
}

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


5. 翻訳済みメタデータ

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

メタデータ文字列を翻訳するには、gt-next の 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 要件実装
URL にロケールを含める[locale] 動的セグメントを使う createNextMiddleware()
HTML lang 属性ルートレイアウトで useLocale() を使用
カノニカル URLgetLocale() + Next.js metadata API alternates.canonical
hreflang タグ対応するすべてのロケールに対する Next.js metadata API alternates.languages
翻訳済みメタデータページタイトルと説明に getGT() を使用
多言語サイトマップロケールごとのエントリと hreflang の代替情報を含む sitemap.ts

次のステップ