Next.js 多言語サイトのSEO完全ガイド:検索エンジンの正しいインデックス戦略

はじめに
あなたはこんな悩みを抱えていませんか?
- 苦労して作った多言語サイトなのに、検索エンジンが間違った言語バージョンを表示する
- 日本のユーザーが検索したのに、英語のページが表示されてしまう
- 異なる言語バージョン同士が検索結果で競合し(カニバリゼーション)、全体の順位を下げている
これらは、多言語サイト特有の SEO 設定ミスが原因です。Google のデータによると、多言語サイトの60%以上が hreflang 設定に誤りがあると言われており、これが国際的な展開の大きな妨げとなっています。
この記事では、Next.js で多言語 SEO を正しく実装するための完全なガイドを提供します。
- hreflang タグの正しい設定 - 言語バージョンの混乱を防ぐ
- 多言語サイトマップの生成 - インデックス登録を加速させる
- URL 構造のベストプラクティス - 最適な国際化戦略の選択
- よくある間違いと修正方法 - 問題の早期発見と解決
Pages Router でも App Router でも対応できる内容になっています。
1. 多言語 SEO の核心概念を理解する
1.1 hreflang とは何か
hreflang は、検索エンジンにそのページの「言語」と「地域」を伝えるための HTML 属性です。主な役割は3つあります。
- 重複コンテンツペナルティの回避 - 異なる言語版が「翻訳された同一コンテンツ」であることを伝え、コピーコンテンツ扱いされるのを防ぎます。
- ユーザーターゲティング - 検索ユーザーの言語設定や地域に基づいて、最適なページを表示します。
- ユーザー体験の向上 - 直帰率を下げ、コンバージョン率を上げます。
1.2 Google は多言語コンテンツをどう処理するか
Google のクローラーが多言語サイトを訪れるとき、以下の手順で処理します。
- ページの言語を検出(HTMLのlang属性、内容分析など)
hreflangタグを探し、他の言語バージョンとの関係を理解する- 検索ユーザーの言語設定に合わせて、最適なバージョンを検索結果に表示する
- 各言語バージョンの評価を統合する(競合させるのではなく)
1.3 よくある SEO 失敗事例
失敗1:hreflang タグの欠落
<!-- ❌ 間違い:hreflang がない -->
<head>
<title>My Website</title>
<link rel="canonical" href="https://example.com/en/about" />
</head>結果:検索エンジンはページ間の関係を理解できず、意図しない言語が表示される可能性があります。
失敗2:hreflang の相互リンク(自己参照)忘れ
<!-- 英語ページ -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="ja" href="https://example.com/ja/about" />
<!-- ❌ 日本語ページ - 間違い:タグが足りない -->
<!-- すべての言語ページで、すべての言語バージョン(自分自身含む)へのリンクが必要です -->結果:Google は双方向の確認が取れない hreflang タグを無視します。
失敗3:誤った言語コード
<!-- ❌ 間違い:存在しない言語コード -->
<link rel="alternate" hreflang="jp" href="..." /> <!-- 日本語は ja です -->
<link rel="alternate" hreflang="en-us" href="..." /> <!-- en-US(大文字小文字は区別されませんが、標準に従うべき) -->結果:無効なコードとして無視されます。
2. URL 戦略の選択
実装に入る前に、適切な URL 構造を選ぶ必要があります。これは SEO、ユーザー体験、技術的実装のすべてに影響します。
2.1 主な3つの戦略比較
| 戦略 | 例 | SEO 効果 | 実装難易度 | 推奨度 |
|---|---|---|---|---|
| サブディレクトリ | example.com/en/ example.com/ja/ | ⭐⭐⭐⭐⭐ 最高 | ⭐⭐⭐ 普通 | ⭐⭐⭐⭐⭐ |
| サブドメイン | en.example.com ja.example.com | ⭐⭐⭐ 普通 | ⭐⭐⭐⭐ 複雑 | ⭐⭐⭐ |
| URL パラメータ | example.com?lang=en | ⭐⭐ 低い | ⭐⭐⭐⭐⭐ 簡単 | ⭐⭐ |
2.2 各戦略の詳細分析
戦略1:サブディレクトリ(推奨)
メリット:
- ドメインパワーが分散せず、サイト全体の評価が向上しやすい
- SSL証明書やDNS管理が1つで済む
- Next.js がネイティブサポートしており、実装が容易
デメリット:
- サーバーの地理的な分散が(CDNを使わない場合)難しい
Next.js 実装設定:
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'ja', 'zh', 'de'],
defaultLocale: 'en',
localeDetection: true // ユーザーの言語を自動検出
}
}戦略2:サブドメイン
メリット:
- 地域ごとにサーバーを分けやすい(例:中国向けサーバーを分離)
- サイトごとに全く異なる技術スタックを使える
デメリット:
- 検索エンジンによっては別サイトとみなされ、SEO評価が分散する可能性がある
- 管理コストが高い
戦略3:URL パラメータ(非推奨)
メリット:
- とにかく実装が簡単
デメリット:
- SEO に最悪。検索エンジンがパラメータを無視することがある
- URL が汚くなる
- 地域ターゲティングができない
結論:
ほとんどのプロジェクトにおいて、サブディレクトリ戦略を強く推奨します。
3. hreflang 設定 完全ガイド
3.1 hreflang タグの役割
hreflang は以下の3点を伝えます:
- このページには他言語版があること
- そのURLはどこか
- その言語・地域コードは何か
3.2 Next.js App Router での設定
方法1:Metadata API を使用(推奨)
Next.js 13以降の App Router では、Metadata API で簡単に設定できます。
// app/[lang]/about/page.tsx
import { Metadata } from 'next'
type Props = {
params: { lang: string }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { lang } = params
// サポートする言語リスト
const languages = ['en', 'ja', 'zh', 'de']
// 全言語の alternative リンクを生成
const alternates = {
canonical: `https://example.com/${lang}/about`,
languages: languages.reduce((acc, locale) => {
acc[locale] = `https://example.com/${locale}/about`
return acc
}, {} as Record<string, string>)
}
return {
title: 'About Us',
alternates,
// x-default はデフォルト言語(マッチしない場合用)
other: {
'x-default': 'https://example.com/en/about'
}
}
}
export default function AboutPage({ params }: Props) {
return <div>About page in {params.lang}</div>
}方法2:カスタム Head コンポーネント
より細かい制御が必要な場合に適しています。
// components/I18nHead.tsx
import Head from 'next/head'
interface I18nHeadProps {
currentLang: string
pathname: string
languages?: string[]
}
export default function I18nHead({
currentLang,
pathname,
languages = ['en', 'ja', 'zh', 'de']
}: I18nHeadProps) {
const baseUrl = 'https://example.com'
return (
<Head>
{/* カノニカルURL */}
<link rel="canonical" href={`${baseUrl}/${currentLang}${pathname}`} />
{/* hreflang タグ */}
{languages.map(lang => (
<link
key={lang}
rel="alternate"
hrefLang={lang}
href={`${baseUrl}/${lang}${pathname}`}
/>
))}
{/* x-default */}
<link
rel="alternate"
hrefLang="x-default"
href={`${baseUrl}/en${pathname}`}
/>
</Head>
)
}3.3 Next.js Pages Router での設定
Pages Router では以下のようにします。
// pages/about.tsx
import { GetStaticProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'
export default function AboutPage() {
const router = useRouter()
const { locale, locales, asPath } = router
const baseUrl = 'https://example.com'
return (
<>
<Head>
<link rel="canonical" href={`${baseUrl}/${locale}${asPath}`} />
{locales?.map(loc => (
<link
key={loc}
rel="alternate"
hrefLang={loc}
href={`${baseUrl}/${loc}${asPath}`}
/>
))}
<link
rel="alternate"
hrefLang="x-default"
href={`${baseUrl}/en${asPath}`}
/>
</Head>
<div>About page content</div>
</>
)
}3.4 地域コードを含む高度な設定
特定の国に向けてコンテンツを出し分けたい場合(例:アメリカ英語とイギリス英語)、言語コード-地域コード の形式を使います。
const hreflangConfig = {
'en-US': 'https://example.com/en-us/about', // アメリカ英語
'en-GB': 'https://example.com/en-gb/about', // イギリス英語
'zh-CN': 'https://example.com/zh-cn/about', // 中国簡体字
'zh-TW': 'https://example.com/zh-tw/about', // 台湾繁体字
'ja-JP': 'https://example.com/ja-jp/about', // 日本語
}3.5 よくある間違いと修正
間違い1:自己参照(Self-referencing)がない
<!-- ❌ 自分自身へのリンクがない -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<!-- ✅ 正しい:自分(ja)を含める -->
<link rel="alternate" hreflang="ja" href="https://example.com/ja/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />なぜ必須か?
Google の仕様で、hreflang は「相互的」である必要があるためです。AページがBページを指すなら、BページもAページを指し、かつA・Bそれぞれが自分自身も指す必要があります。
間違い2:x-default がない
<!-- ✅ 推奨:x-default を設定 -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />x-default は、ユーザーの言語設定がいずれの hreflang にもマッチしなかった場合に表示される「フォールバックページ」を指定します。通常は英語版か、言語選択ページを指定します。
間違い3:hreflang と canonical の矛盾
canonical(正規化タグ)は、そのページ自身のURLを指すべきです。別言語のページを指してはいけません。
<!-- ❌ 間違い:日本語ページの canonical が英語ページを向いている -->
<link rel="canonical" href="https://example.com/en/about" />
<!-- ✅ 正しい:日本語ページの canonical は日本語ページを向く -->
<link rel="canonical" href="https://example.com/ja/about" />4. 多言語サイトマップの実装
サイトマップは検索エンジンにページの存在を教える重要な地図です。
4.1 なぜ多言語サイトマップが必要か
- インデックス速度の向上 - クローラーがリンクを辿るのを待たずにページを通知できる
- 網羅性の確保 - 深い階層にあるページも確実に伝える
- hreflang情報の補完 - サイトマップ内にも hreflang 情報を記述可能
4.2 サイトマップ戦略
戦略1:単一サイトマップ(小規模サイト向け)
5000ページ以下のサイトなら、すべてのURLを1つの sitemap.xml にまとめます。
<?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/ja/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
</url>
<!-- 他の言語も同様に -->
</urlset>戦略2:言語別サイトマップ(中〜大規模サイト向け)
言語ごとにサイトマップを分割し、インデックスファイル(sitemap-index.xml)でまとめます。
<!-- sitemap-index.xml -->
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-ja.xml</loc>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-en.xml</loc>
</sitemap>
</sitemapindex>4.3 Next.js App Router での自動生成
app/sitemap.ts を使うと動的に生成できます。
// app/sitemap.ts
import { MetadataRoute } from 'next'
const languages = ['en', 'ja', 'zh']
const baseUrl = 'https://example.com'
async function getBlogPosts() {
// DBやCMSから記事を取得
return [{ slug: 'hello-world', date: '2025-01-01' }]
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const sitemap: MetadataRoute.Sitemap = []
// 静的ページ
const routes = ['', '/about', '/contact']
routes.forEach(route => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}${route}`,
lastModified: new Date(),
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}${route}`
return acc
}, {} as Record<string, string>)
}
})
})
})
// ブログ記事(動的ページ)
const posts = await getBlogPosts()
posts.forEach(post => {
languages.forEach(lang => {
sitemap.push({
url: `${baseUrl}/${lang}/blog/${post.slug}`,
lastModified: new Date(post.date),
alternates: {
languages: languages.reduce((acc, l) => {
acc[l] = `${baseUrl}/${l}/blog/${post.slug}`
return acc
}, {} as Record<string, string>)
}
})
})
})
return sitemap
}4.4 サイトマップの送信
生成しただけでは不十分です。検索エンジンに通知しましょう。
- robots.txt に記述
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml- Google Search Console に登録
- Bing Webmaster Tools に登録
5. ベストプラクティスと注意点
5.1 コンテンツ品質と自動翻訳
SEOにおいて「翻訳の品質」は非常に重要です。
絶対にやってはいけないこと:
- ❌ Google翻訳などの自動翻訳結果をそのまま掲載する(低品質コンテンツとみなされペナルティ対象になる可能性があります)
- ❌ ナビゲーションだけ翻訳して、本文は元の言語のままにする
- ❌ 言語によって情報量に極端な差をつける
やるべきこと:
- ✅ ネイティブスピーカーによる翻訳、または校正を入れる
- ✅ 単なる翻訳ではなく「ローカライズ」(文化に合わせた修正)を行う
5.2 自動翻訳の SEO リスク
JavaScript によるクライアントサイド自動翻訳ウィジェットは SEO に効果がありません。クローラーは実行前の原文しか見ないからです。必ずサーバーサイドでレンダリングされた翻訳コンテンツを提供してください。
5.3 パフォーマンスへの配慮
- CDN の活用:世界中のユーザーに高速に配信するため、Vercel Edge Network や Cloudflare などを活用しましょう。
- 画像の最適化:画像内のテキストは翻訳できないため、可能な限りテキストは HTML で記述し、画像は共通化するか、言語ごとに差し替え可能な仕組みを作りましょう。
5.4 継続的な監視
- Google Search Console の「インターナショナル ターゲティング」レポート(旧版)や、カバレッジレポートでエラーがないか定期的に確認します。
- Ahrefs などの外部ツールを使って、hreflang の整合性を監査します。
多言語 SEO は一度設定して終わりではありません。新しいページが増えるたびに、正しくリンク構造が保たれているか確認し続けることが成功への鍵です。
4 min read · 公開日: 2025年12月25日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践

Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド

Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド


コメント
GitHubアカウントでログインしてコメントできます