next-intlは、Next.jsのApp Routerに多言語対応を組み込むためのオープンソースの国際化ライブラリです。npm registryで2026年11月1日に確認した最新版は4.14.9(10月2日公開)で、ライセンスはMITでした。この記事では、next-intlが受け持つ範囲と初期設定、[locale]セグメントとproxy.tsで言語別URLを作る構成、Next.js 16.3以降の静的レンダリング、ICU形式のメッセージを名前空間で分ける設計、ブラウザへ渡す翻訳の絞り込み、i18nextとの選び分けまでを、公式ドキュメントの記述をもとに実装の順番で整理します。
まとめ:next-intlを選ぶ条件とApp Routerで先に決めるURL設計とメッセージの分け方
Next.jsのApp Routerだけで完結するサイトを多言語化するなら、next-intlが第一候補になります。言語の判定、URLへの言語の付与、Server ComponentsとClient Componentsの両方での翻訳取得が1つのパッケージにまとまっており、Next.js公式の国際化ガイドで自前実装する部分をほぼ肩代わりします。
着手前に決めるのは、言語をURLに載せるかどうか、メッセージをどの単位で分けるか、ブラウザへ翻訳をどこまで渡すかの3点です。メール本文やバッチ処理などNext.jsの外でも同じ翻訳を使うなら、話は変わります。その場合はi18nextを軸にしたほうが翻訳資産を共有できます。
next-intlの定義|Next.js公式ガイドが自前実装に任せた部分を埋める役割
最初に、Next.js本体が国際化について何を用意し、何を利用者側に残しているかを確かめます。
公式ガイドが挙げる8ライブラリの1つとしての位置づけと受け持ち範囲
Next.jsの国際化ガイドは、App Routerでの多言語化を、proxyでAccept-Languageヘッダーを見て言語別のパスへ振り分け、app/[lang]配下で言語ごとの辞書JSONを読み込む手順として説明しています。辞書の読み込みも振り分けの処理も、サンプルコードを自分で書く前提です。同じページの末尾には、next-intl、next-international、paraglide-next、linguiなど8つのライブラリが参考資料として並んでいます。
next-intlはこの自前部分をまとめて引き受けます。言語の判定とリダイレクト、言語付きのLink、翻訳文の取得、日付や数値の書式、複数形の切り替えです。依存関係を見ると、React向けのuse-intlを土台にし、Next.js固有の処理を上に載せる2層構成になっています。国際化(i18n)という用語自体の整理はi18nとl10nの違いとAngularでの実装手順で扱っています。
4.14.9の動作要件|Next.js 12〜16系・React 19の対応範囲
npm registryの記録では、現行の4系は4.0.0が2025年3月12日の公開です。2026年4〜5月には4.9.0から4.13.0までのマイナー版が続けて出ており、4.14.0は8月27日の公開でした。4.14.9のリリースは10月2日でした。peerDependenciesはNext.jsが12〜16系、Reactが16.8以上から19系までと幅広く、Next.js 16.4.3(10月23日公開)でも範囲内に収まります。
4.0の告知によると、4系はTypeScript 5以上が前提で、next-intl/pluginを除いてESM専用のパッケージになりました。3系から上げる場合、型の登録方法とCookieの既定値も変わっているため、後述の2か所を確認します。
next-intlの導入手順|言語別ルーティングなしで翻訳を表示する構成
言語別URLを作る前に、まず1言語で翻訳が画面に出るところまでを通します。App Router向けの導入手順もこの順番です。
導入時の3工程|パッケージの導入とリクエスト設定・プラグインの登録
npm installでパッケージを入れ、メッセージのJSONとリクエストごとの設定ファイルi18n/request.tsを作り、next.config.tsにプラグインを登録します。
# パッケージを入れる
npm install next-intl
// messages/ja.json
{
"HomePage": { "title": "ようこそ" }
}
// src/i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async () => {
const locale = 'ja'; // 後でURLやCookieから決める
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});
// next.config.ts
import type {NextConfig} from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const nextConfig: NextConfig = {};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
プラグインはsrc/i18n/request.tsを既定の場所として探します。別の場所に置くならcreateNextIntlPlugin()の引数にパスを渡します。メッセージを動的importで読む書き方にしておくと、言語が増えても使う言語のJSONしか読み込まれません。
翻訳APIの選択基準|サーバーとクライアントで異なる非同期処理の扱い
翻訳の取り出し方を分ける基準は、コンポーネントが非同期かどうかです。asyncのServer Componentではnext-intl/serverのgetTranslationsをawaitし、同期のコンポーネントではuseTranslationsを使います。後者はServer ComponentとClient Componentのどちらでも動きます。
// src/app/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
import {getLocale} from 'next-intl/server';
export default async function RootLayout({children}: {children: React.ReactNode}) {
const locale = await getLocale();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
// src/app/page.tsx(非同期のServer Component)
import {getTranslations} from 'next-intl/server';
export default async function HomePage() {
const t = await getTranslations('HomePage');
return <h1>{t('title')}</h1>;
}
<NextIntlClientProvider>はClient ComponentsでuseTranslationsを使うための入れ物です。4系からは、propsを渡さなくてもrequest.tsで返したメッセージと書式を自動で受け継ぎます。3系でmessagesを手で渡していたコードは、そのまま残すと二重指定になるので消して構いません。Server ComponentsとClient Componentsの境界そのものはReact Server Componentsの使い分けが前提知識になります。
言語別URLの設計|[locale]セグメントとproxy.tsで言語を振り分ける構成
検索結果に言語ごとのページを載せたいなら、/ja/と/en/のようにURLへ言語を含めます。ここからが、next-intlを入れる価値の大きい部分です。
言語別URLの3ファイル構成|ルーティング定義・振り分け・ナビゲーション
役割の対応は、言語の定義がrouting.ts、振り分けがproxy.ts、言語付きリンクがnavigation.tsです。ルーティングの設定手順では、対応言語の定義、リクエストの振り分け、言語付きの画面遷移APIの3つを別ファイルに分けます。ページ類はすべてapp/[locale]/の下へ移します。
// src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['ja', 'en'],
defaultLocale: 'ja'
});
// src/proxy.ts(Next.js 15以前は middleware.ts)
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
// api・_next・拡張子付きのファイルは振り分けない
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};
// src/i18n/navigation.ts
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation(routing);
同じ手順書には、proxy.tsはNext.js 16より前はmiddleware.tsという名前だったと明記されています。ネット上の解説の多くはmiddleware.tsのままなので、16系でそのまま写す場合に戸惑いやすいのは、ファイル名の違いです。matcherはドットを含むパスを除外するため、/users/jane.doeのようなURLを扱うアプリでは除外条件を足す必要があります。[locale]のような動的セグメントの規約はApp Routerのファイル規約と動的ルーティングで解説しています。
localePrefixの3値とCookie|as-needed選択時の確認事項
ルーティング設定の説明によると、localePrefixの既定値は'always'で、すべてのパスが/ja/aboutのように言語から始まります。既定言語だけ接頭辞を省く'as-needed'と、URLに言語を出さない'never'も選べます。
| 設定値 | 既定言語のURL | 向いている場面 |
|---|---|---|
| ‘always’(既定) | /ja/about | 言語ごとに検索結果へ載せる |
| ‘as-needed’ | /about | 既存の日本語URLを変えたくない |
| ‘never’ | /about | ログイン後の管理画面 |
既定のままでも、言語の判定はAccept-LanguageヘッダーとNEXT_LOCALEというCookieで行われます。4.0からこのCookieはmax-ageを付けないセッションCookieになり、ブラウザを閉じると消える仕様です。言語の選択を次回の訪問まで覚えさせたいなら、localeCookieでmaxAgeを指定します。
既存サイトを多言語化するときは、日本語URLを動かさずに済む'as-needed'を選びがちです。ただし接頭辞の無いURLに来た英語ブラウザの利用者がどう振り分けられるかを、公開前に必ず確かめます。proxyは既定でlinkヘッダーにhreflangの代替リンクを出すので、HTML側にも同じ指定を書く場合は、二重定義を避けるために指定内容の統一が必要です。hreflangの書き方と検証はhreflangの書き方と多言語サイトの検証手順にまとめています。
Next.js 16.3以降の静的レンダリング|言語パラメーターの取得設定
[locale]のような動的セグメントを使うと、何もしなければページはリクエストごとの動的レンダリングになります。従来は各レイアウトとページでsetRequestLocale(locale)を呼んで回避していました。手順書は現在これを旧APIと位置づけ、next/root-paramsで言語を読む構成を勧めています。
// src/i18n/request.ts(ルーティングあり)
import * as rootParams from 'next/root-params';
import {notFound} from 'next/navigation';
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
export default getRequestConfig(async ({locale}) => {
if (!locale) {
const paramValue = await rootParams.locale();
if (hasLocale(routing.locales, paramValue)) {
locale = paramValue;
} else {
notFound(); // 未対応の言語は404にする
}
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});
// src/app/[locale]/layout.tsx(ビルド時に生成する言語を列挙)
import {routing} from '@/i18n/routing';
export function generateStaticParams() {
return routing.locales.map((locale) => ({locale}));
}
next-intlの手順書によると、next/root-paramsはNext.js 16.3以降なら既定で使え、それより前はexperimental.rootParamsでの有効化が必要です。Next.jsのroot-paramsリファレンスにあるとおり、取得できるのはルートレイアウトより上の動的セグメントだけです。app/[locale]/layout.tsxをルートレイアウトにする配置が前提になります。16.2以前に留まる案件や、app/layout.tsxを残す構成なら、旧来のsetRequestLocaleを全レイアウトとページで呼ぶ方式を続けます。SSGとSSRの違いはNext.jsのレンダリング方式の選び方を参照してください。
メッセージ分割の設計|名前空間の切り方とブラウザへ渡す翻訳量の絞り込み
翻訳が数百キーを超えると、メッセージの置き方がそのままビルドとブラウザの転送量に響きます。書き方、渡し方、型の3つに分けて決めます。
画面単位の名前空間とICU形式の複数形・select・t.richの書き方
翻訳の描画の説明のとおり、next-intlのメッセージはICU MessageFormatで書きます。複数形や性別による文言の切り替えを、キーを分けずに1つの文の中で表現する形式です。
// messages/ja.json
{
"Cart": {
"itemCount": "{count, plural, =0 {カートは空です} other {#点の商品}}",
"notice": "<terms>利用規約</terms>に同意して購入します"
}
}
// 呼び出し側
const t = useTranslations('Cart');
t('itemCount', {count: 3}); // 「3点の商品」
t.rich('notice', {
terms: (chunks) => <a href="/terms">{chunks}</a>
});
useTranslations('Cart')の引数が名前空間で、JSONの最上位キーを画面単位にしておくと、コンポーネントごとに必要な塊だけを指名できます。名前空間のキーにはピリオドを使えません。i18nextのように_oneや_otherのキーを並べる方式とは書き方が根本から違うため、既存の翻訳ファイルを持ち込むなら変換作業が発生します。t.rawはメッセージを解釈せずに返すので、HTMLを含む値を描画するときは自前でサニタイズします。
翻訳の転送量を絞る設計|全メッセージを渡す既定動作とサーバーでの翻訳
Server ComponentsとClient Componentsの説明によると、<NextIntlClientProvider>は既定で全メッセージをクライアントへ渡します。始めるには楽な一方、翻訳がそのままブラウザ向けの転送量に乗ります。
公式が勧める形は、翻訳をServer Componentで済ませ、訳した文字列をpropsやchildrenでClient Componentへ渡す方法です。開閉ボタンのように操作だけをクライアントで持つ部品なら、Client Componentは翻訳APIを呼ばずに済みます。どうしてもクライアント側で訳す部品には、useMessages()で取った全体から必要な名前空間だけを抜き出してmessagesに渡すのが公式の例です。同じ説明では、この絞り込みは計測して遅いと分かってから行えばよいとも書かれています。翻訳が数十キーの段階で手を付ける必要はありません。
翻訳の型検査の設定|AppConfigの拡張とメッセージ引数の宣言生成
キーと言語の検査にはAppConfig、引数の検査にはcreateMessagesDeclarationを設定する構成です。TypeScriptの型拡張の説明に沿い、AppConfigに言語とメッセージの型を登録します。存在しないキーや未対応の言語コードを渡すと、コンパイルの時点でエラーになります。
// global.ts
import {routing} from '@/i18n/routing';
import messages from './messages/ja.json';
declare module 'next-intl' {
interface AppConfig {
Locale: (typeof routing.locales)[number];
Messages: typeof messages;
}
}
// next.config.ts({count}などの引数まで型検査する場合)
const withNextIntl = createNextIntlPlugin({
experimental: {
createMessagesDeclaration: './messages/ja.json'
}
});
引数の型検査を足す場合は、tsconfig.jsonでallowArbitraryExtensionsもtrueにします。next devかnext buildの実行時にmessages/ja.d.json.tsという宣言ファイルが生成され、これは.gitignoreに入れて構いません。型の基準にするJSONは、最も網羅的な言語のファイルを選びます。日本語が原文ならja.jsonです。
next-intlとi18next系・自前辞書の違いと採用する条件・見送る場面
Next.jsの多言語化には、next-intl以外の道もあります。対象範囲と翻訳ファイルの形式を並べてから線を引きます。
next-intl・next-i18next・自前辞書の対象範囲とメッセージ形式の比較
| 方式 | 最新版(11月1日時点) | 主な対象 | メッセージ形式 |
|---|---|---|---|
| next-intl | 4.14.9 | Next.js | ICU MessageFormat |
| next-i18next | 16.4.5 | Next.js(i18next連携) | i18next形式(キー接尾辞) |
| 公式ガイドの自前辞書 | なし(Next.js 16.4.3) | Next.js | 任意のJSON |
版はnpm registryのlatestタグで確認した値です。next-i18nextの土台であるi18nextは同日時点で26.6.0でした。i18nextは同じ翻訳ファイルをReact、Vue、Node.jsのサーバー処理で読める点が強みで、詳しくは同じG1のi18nextの翻訳ファイル設計とSSR読み込みの実装で整理しています。自前辞書は依存が増えない代わりに、複数形、日付の書式、言語付きリンクを全部書くことになります。
next-intlを採用してよい条件と別の方式を選ぶべき2つの場面
採用してよいのは、App Routerだけで完結するサイトで、言語別URL、静的レンダリング、型付きの翻訳キーを揃えたい場合です。どれも設定ファイル数本で済み、4.14.9のpeerDependenciesがNext.js 16系まで含んでいるため、現行の版でそのまま組めます。
見送る場面は2つです。第一に、Next.jsの外でも同じ翻訳を使う場合。注文確認メールやPDF帳票をNode.jsのバッチで多言語生成するなら、i18nextに翻訳資産を集めたほうが二重管理を避けられます。第二に、2言語で数十キーしかない小規模サイト。公式ガイドの自前辞書とproxyで十分に回り、ライブラリの版上げを追う手間のほうが大きくなります。
多言語サイトの構築を外部に依頼する際に要件定義で固める3項目
next-intlの導入そのものは半日で終わる規模の作業です。工数がかかるのは、翻訳を誰が書き、どの経路で更新して公開するかという運用の設計にあります。外部に構築を依頼するなら、localePrefixの値、名前空間の単位、ブラウザへ渡す翻訳の範囲の3つを要件定義で決めておくと、公開後にURLを付け替える事態を避けられます。
サイト全体の言語やURLの設計は多言語サイト制作の進め方と言語・URL設計の判断基準で解説しています。当社は英語サイト制作・多言語サイト制作として、ライブラリの選定から翻訳の更新手順の設計まで含めた構築を受託しています。
よくある質問
next-intlの導入検討でよく挙がる疑問を、公式ドキュメントとnpm registryの記録をもとに整理します。
next-intlは無料で商用利用できますか?
できます。npm registryに登録されたnext-intl 4.14.9のライセンスはMITで、商用のサイトやアプリでの利用と改変に制約はありません。ソースはGitHubのリポジトリで公開されています。費用が出るのはライブラリの外側で、翻訳管理サービスの契約や翻訳作業の外注が該当します。
Pages Routerのアプリでもnext-intlは使えますか?
使えます。peerDependenciesはNext.js 12系から対象に含めており、公式ドキュメントにもPages Router向けの導入手順があります。ただしPages RouterにはNext.js本体のi18n設定による言語別ルーティングがあるため、next-intlが受け持つのは主に翻訳文の取得と書式です。App Routerへ移行する予定があるなら、移行後も同じメッセージファイルを使える点が利点になります。
middleware.tsとproxy.tsはどちらに書けばよいですか?
Next.js 16系ならproxy.ts、15系以前ならmiddleware.tsです。next-intlのルーティング手順書に、proxy.tsはNext.js 16より前はmiddleware.tsと呼ばれていたと書かれています。中身のcreateMiddleware(routing)は同じなので、版上げのときはファイル名を変えるだけで済みます。Next.jsの版の確かめ方はNext.js 16のリリース履歴と更新手順を参照してください。
翻訳キーを手で付けずに済む方法はありますか?
実験的な機能としてuseExtractedがあります。コードに原文を直接書くと、next devやnext buildの実行時にメッセージファイルへ自動で抽出される仕組みです。next.config.tsのプラグイン設定でexperimental.extractを有効にし、保存形式はJSONかPOを選びます。実験段階のAPIなので、長く保守する案件では通常のuseTranslationsで始めるのが無難です。
日本語と英語の2言語だけでもnext-intlを入れる意味はありますか?
言語別URLで検索結果に載せたいなら意味があります。言語の判定とリダイレクト、言語付きのLink、未対応の言語を404にする処理を自前で書かずに済むためです。逆に、ログイン後の管理画面だけを英語にも切り替えられるようにしたい程度なら、URLを分けない構成か、公式ガイドの自前辞書で足ります。