Menu

国際化(i18n)ルーティング

Next.jsはv10.0.0から国際化(i18n)ルーティングの組み込みサポートを提供しています。ロケールのリスト、デフォルトロケール、ドメイン固有のロケールを指定すると、Next.jsが自動的にルーティングを処理します。

現在のi18nルーティングサポートは、react-intlreact-i18nextlinguirosettanext-intlnext-translatenext-multilingualtolgeeparaglide-nextなどの既存のi18nライブラリソリューションを補完することを目的としており、ルートとロケールの解析を合理化します。

はじめに

開始するには、next.config.jsファイルにi18nの設定を追加します。

ロケールはUTS ロケール識別子で、ロケールを定義するための標準化された形式です。

通常、ロケール識別子は、ダッシュで区切られた言語、地域、スクリプトで構成されます:language-region-script。地域とスクリプトはオプションです。例:

  • en-US - アメリカ合衆国で話されている英語
  • nl-NL - オランダで話されているオランダ語
  • nl - オランダ語、特定の地域なし

ユーザーのロケールがnl-BEで、設定に記載されていない場合、利用可能であればnlに、そうでない場合はデフォルトロケールにリダイレクトされます。 国の全地域をサポートする予定がない場合、フォールバックとして機能する国のロケールを含めることをお勧めします。

next.config.js
module.exports = {
  i18n: {
    // アプリケーションでサポートするすべてのロケール
    locales: ['en-US', 'fr', 'nl-NL'],
    // ロケール接頭辞のないパス(例:`/hello`)にアクセスした際に使用するデフォルトロケール
    defaultLocale: 'en-US',
    // ロケールドメインとそのデフォルトロケールのリスト(ドメインルーティングを設定する場合のみ必要)
    // 注意:サブドメインはドメイン値に含める必要があります(例:「fr.example.com」)
    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
        // オプションでhttpフィールドを使用して、
        // httpsではなくhttpでローカルにロケールドメインをテストすることもできます
        http: true,
      },
    ],
  },
}

ロケール戦略

ロケール処理戦略には、サブパスルーティングとドメインルーティングの2つがあります。

サブパスルーティング

サブパスルーティングは、URLパスにロケールを配置します。

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL'],
    defaultLocale: 'en-US',
  },
}

上記の設定では、en-USfrnl-NLがルーティング可能で、en-USがデフォルトロケールになります。pages/blog.jsがある場合、以下のURLが利用可能になります:

  • /blog
  • /fr/blog
  • /nl-nl/blog

デフォルトロケールには接頭辞がありません。

ドメインルーティング

ドメインルーティングを使用すると、異なるドメインからロケールを提供できます:

next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'fr', 'nl-NL', 'nl-BE'],
    defaultLocale: 'en-US',
 
    domains: [
      {
        // 注意:サブドメインはドメイン値に含める必要があります
        // 例:予期されるホスト名が www.example.com の場合は、それを使用する必要があります
        domain: 'example.com',
        defaultLocale: 'en-US',
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr',
      },
      {
        domain: 'example.nl',
        defaultLocale: 'nl-NL',
        // このドメインにリダイレクトされるべき
        // 他のロケールを指定
        locales: ['nl-BE'],
      },
    ],
  },
}

例えば、pages/blog.jsがある場合、以下のURLが利用可能になります:

  • example.com/blog
  • www.example.com/blog
  • example.fr/blog
  • example.nl/blog
  • example.nl/nl-BE/blog

自動ロケール検出

ユーザーがアプリケーションのルート(通常は/)にアクセスすると、Next.jsはAccept-Languageヘッダーと現在のドメインに基づいて、ユーザーが好むロケールを自動的に検出しようとします。

デフォルトロケール以外のロケールが検出された場合、ユーザーは以下のいずれかにリダイレクトされます:

  • サブパスルーティングを使用する場合: ロケール接頭辞のパス
  • ドメインルーティングを使用する場合: そのロケールをデフォルトとして処理するドメイン

ドメインルーティングを使用する場合、Accept-Languageヘッダーがfr;q=0.9のユーザーがexample.comにアクセスすると、そのドメインがデフォルトでfrロケールを処理するため、example.frにリダイレクトされます。

サブパスルーティングを使用する場合、ユーザーは/frにリダイレクトされます。

デフォルトロケールの接頭辞付け

Next.js 12とミドルウェアを使用すると、ワークアラウンドによってデフォルトロケールに接頭辞を追加できます。

例えば、以下は数か国語をサポートするnext.config.jsファイルです。「default」ロケールが意図的に追加されていることに注意してください。

next.config.js
module.exports = {
  i18n: {
    locales: ['default', 'en', 'de', 'fr'],
    defaultLocale: 'default',
    localeDetection: false,
  },
  trailingSlash: true,
}

次に、ミドルウェアを使用してカスタムルーティングルールを追加できます:

middleware.ts
import { NextRequest, NextResponse } from 'next/server'
 
const PUBLIC_FILE = /\.(.*)$/
 
export async function middleware(req: NextRequest) {
  if (
    req.nextUrl.pathname.startsWith('/_next') ||
    req.nextUrl.pathname.includes('/api/') ||
    PUBLIC_FILE.test(req.nextUrl.pathname)
  ) {
    return
  }
 
  if (req.nextUrl.locale === 'default') {
    const locale = req.cookies.get('NEXT_LOCALE')?.value || 'en'
 
    return NextResponse.redirect(
      new URL(`/${locale}${req.nextUrl.pathname}${req.nextUrl.search}`, req.url)
    )
  }
}

このミドルウェアは、APIルートや、フォントや画像などの公開ファイルへの接頭辞の追加をスキップします。デフォルトロケールへのリクエストがある場合、/enにリダイレクトします。

自動ロケール検出の無効化

自動ロケール検出は、以下のように無効にできます:

next.config.js
module.exports = {
  i18n: {
    localeDetection: false,
  },
}

localeDetectionfalseに設定されている場合、Next.jsはユーザーの優先ロケールに基づいて自動的にリダイレクトしなくなり、上記で説明したロケールベースのドメインまたはロケールパスから検出されたロケール情報のみを提供します。

ロケール情報へのアクセス

Next.jsルーターを通じてロケール情報にアクセスできます。例えば、useRouter()フックを使用すると、以下のプロパティが利用可能です:

  • locale:現在アクティブなロケールを含みます。
  • locales:設定されているすべてのロケールを含みます。
  • defaultLocale:設定されているデフォルトロケールを含みます。

getStaticPropsまたはgetServerSideProps事前レンダリングするページでは、関数に提供されるコンテキストにロケール情報が提供されます。

getStaticPathsを利用する場合、設定されたロケールは関数のコンテキストパラメーターのlocalesで提供され、設定されたデフォルトロケールはdefaultLocaleで提供されます。

ロケール間の遷移

next/linkまたはnext/routerを使用してロケール間を移動できます。

next/linkの場合、現在のロケールとは異なるロケールに遷移するためにlocaleプロップを指定できます。localeプロップが提供されない場合、クライアント遷移中は現在のアクティブなlocaleが使用されます。例:

import Link from 'next/link'
 
export default function IndexPage(props) {
  return (
    <Link href="/another" locale="fr">
      To /fr/another
    </Link>
  )
}

next/routerメソッドを直接使用する場合、遷移オプションを通じて使用するlocaleを指定できます。例:

import { useRouter } from 'next/router'
 
export default function IndexPage(props) {
  const router = useRouter()
 
  return (
    <div
      onClick={() => {
        router.push('/another', '/another', { locale: 'fr' })
      }}
    >
      to /fr/another
    </div>
  )
}

動的ルートのクエリ値や非表示のhrefクエリ値などのルーティング情報をすべて保持しながら、localeのみを切り替える場合は、hrefパラメーターをオブジェクトとして提供できます:

import { useRouter } from 'next/router'
const router = useRouter()
const { pathname, asPath, query } = router
// ルートの他の情報をすべて維持しつつ、ロケールのみを変更
router.push({ pathname, query }, asPath, { locale: nextLocale })

router.pushのオブジェクト構造の詳細はこちらを参照してください。

すでにロケールを含むhrefがある場合、ロケールプレフィックスの自動処理を無効にできます:

import Link from 'next/link'
 
export default function IndexPage(props) {
  return (
    <Link href="/fr/another" locale={false}>
      To /fr/another
    </Link>
  )
}

NEXT_LOCALE クッキーの活用

Next.jsでは、accept-languageヘッダーよりも優先されるNEXT_LOCALE=the-localeクッキーの設定を許可しています。このクッキーは言語切り替えを使用して設定でき、ユーザーがサイトに戻ってきたときに、/からの適切なロケール位置にリダイレクトする際にクッキーで指定されたロケールを活用します。

例えば、ユーザーのaccept-languageヘッダーでfrロケールを希望していても、NEXT_LOCALE=enクッキーが設定されている場合、/を訪問する際はenロケールにリダイレクトされ、クッキーが削除または期限切れになるまでその状態が続きます。

検索エンジン最適化

Next.jsはユーザーが訪問している言語を把握しているため、自動的に<html>タグにlang属性を追加します。

Next.jsはページの variants を認識できないため、next/headを使用してhreflangメタタグを追加する必要があります。hreflangの詳細はGoogle Webmasters ドキュメントで確認できます。

静的生成とどのように連携するか

注意:国際化ルーティングは、Next.jsのルーティングレイヤーを利用しないoutput: 'export'と統合されません。output: 'export'を使用しないハイブリッドNext.jsアプリケーションは完全にサポートされています。

動的ルートとgetStaticPropsページ

動的ルートgetStaticPropsを使用するページの場合、事前レンダリングする必要があるページのすべてのロケールバリアントをgetStaticPathsから返す必要があります。pathsに返されるparamsオブジェクトとともに、レンダリングしたいロケールを指定するlocaleフィールドも返すことができます。例:

pages/blog/[slug].js
export const getStaticPaths = ({ locales }) => {
  return {
    paths: [
      // `locale`が提供されない場合、デフォルトロケールのみが生成されます
      { params: { slug: 'post-1' }, locale: 'en-US' },
      { params: { slug: 'post-1' }, locale: 'fr' },
    ],
    fallback: true,
  }
}

自動的に静的に最適化されたページと非動的getStaticPropsページについては、ページの各ロケール用のバージョンが生成されます。これは、getStaticProps内で構成されているロケールの数に応じてビルド時間が増加する可能性があるため、考慮することが重要です。

例えば、50のロケールと10の非動的getStaticPropsページがある場合、getStaticPropsは500回呼び出され、10ページの50バージョンが各ビルド中に生成されます。

getStaticPropsを使用する動的ページのビルド時間を短縮するには、fallbackモードを使用してください。これにより、ビルド中に最も人気のあるパスとロケールのみをgetStaticPathsから返すことができます。その後、Next.jsは要求に応じてランタイムで残りのページをビルドします。

自動的に静的に最適化されたページ

自動的に静的に最適化されたページについては、ページの各ロケール用のバージョンが生成されます。

非動的 getStaticProps ページ

非動的getStaticPropsページの場合、上記と同様に各ロケール用のバージョンが生成されます。getStaticPropsは、レンダリングされる各localeで呼び出されます。特定のロケールを事前レンダリングから除外したい場合は、getStaticPropsからnotFound: trueを返すことで、そのページのバリアントは生成されません。

export async function getStaticProps({ locale }) {
  // 外部APIエンドポイントを呼び出してポストを取得
  // 任意のデータ取得ライブラリを使用できます
  const res = await fetch(`https://.../posts?locale=${locale}`)
  const posts = await res.json()
 
  if (posts.length === 0) {
    return {
      notFound: true,
    }
  }
 
  // { props: posts } を返すことで、Blogコンポーネントは
  // ビルド時に prop として `posts` を受け取ります
  return {
    props: {
      posts,
    },
  }
}

i18n設定の制限

  • locales:合計100ロケール
  • domains:合計100ロケールドメインアイテム

補足:これらの制限は、ビルド時のパフォーマンス問題を防ぐために初期に追加されました。Next.js 12のミドルウェアを使用するカスタムルーティングでこれらの制限を回避できます。