Configuration i18n de next-intl dans Next.js 16 avec proxy.ts
Configurez next-intl Next.js 16 avec proxy.ts, async params, routes locales statiques et métadonnées App Router i18n pour pages SaaS, étape par étape.

Votre landing page SaaS est prête, mais la version allemande renvoie une 404, le sélecteur de locale envoie les utilisateurs vers la mauvaise URL, et un ancien tutoriel vous dit encore de créer middleware.ts. Les pièces mobiles sont petites, mais elles doivent s’aligner : proxy.ts, params asynchrones, routage par locale, génération statique et metadata. Ce guide montre une configuration next-intl Next.js 16 sûre pour la production.
src/proxy.ts : Next.js 16 utilise proxy.ts pour la négociation de locale au moment de la requête ; la convention de fichier middleware a été renommée Proxy et est dépréciée dans Next.js 16.
params asynchrones : Dans app/[locale]/layout.tsx, typez params comme Promise<{locale: string}>, attendez-le, validez la locale, puis rendez le provider.
- Routes de locale statiques : Utilisez
generateStaticParams() pour retourner les locales prises en charge et appelez setRequestLocale(locale) avant d’utiliser les APIs next-intl dans un layout ou une page statique.
- Navigation sûre pour le SEO : Utilisez les helpers de routage next-intl afin que les liens localisés, redirections, pathnames et URLs de langues alternatives restent cohérents.
- Payloads client : Pour les applications plus grandes, gardez la plupart du texte des pages dans des Server Components et ne transmettez que les namespaces de messages dont les Client Components ont besoin.
Qu’est-ce que next-intl Next.js 16 ?
next-intl Next.js 16 est le modèle d’intégration App Router actuel pour construire des applications Next.js localisées avec next-intl et src/proxy.ts.
L’objectif n’est pas seulement la recherche de traductions. Une configuration de production a besoin d’URLs conscientes de la locale, d’une négociation de requête, de paramètres de route validés, d’un rendu statique lorsque c’est possible, de metadata localisées et de liens de langue compréhensibles par les moteurs de recherche.
La principale source de bugs vient de la copie d’une arborescence de fichiers de l’époque Next.js 15 dans une application Next.js 16. Les concepts sont familiers, mais la surface d’intégration a suffisamment changé pour que de petits décalages déclenchent des erreurs comme Unable to find next-intl locale ou fassent basculer des routes statiques vers un rendu dynamique.
Pourquoi cette configuration i18n avec App Router diffère des anciens guides
L’App Router ne vous fournit pas à lui seul un système de traduction complet. Next.js prend en charge l’internationalisation via des primitives de routage, des segments de route dynamiques, Proxy et des paramètres statiques. next-intl ajoute le chargement des messages, la négociation de locale, des helpers de routage typés, des APIs de traduction serveur/client et des liens alternatifs orientés SEO.
Pour l’i18n avec Next.js 16, le changement le plus visible est le nom du fichier d’interception des requêtes. Utilisez src/proxy.ts dans une nouvelle configuration. Les anciens articles peuvent encore montrer middleware.ts, qui était la convention de fichier précédente et qui est désormais dépréciée dans Next.js 16.
| Préoccupation | Modèle Next.js 16 + next-intl | Décalage courant dans les anciens guides |
|---|---|---|
| Négociation de requête | src/proxy.ts avec createMiddleware(routing) |
middleware.ts dans l’arborescence de fichiers |
| Routes de locale | app/[locale]/... |
Pages en dehors du segment de locale |
| Paramètres de route | params: Promise<{locale: string}> |
Typage synchrone de params |
| i18n statique | generateStaticParams() plus setRequestLocale(locale) |
Seulement generateStaticParams() |
| Navigation | Helpers createNavigation(routing) |
Concaténation manuelle de chaînes |
Si vous avez déjà une configuration Next.js 15, comparez-la avec cet article plutôt que de changer les noms de fichiers à l’aveugle. L’ancien guide Aniq UI sur l’ajout de next-intl à Next.js 15 reste utile pour les concepts, mais cet article est d’abord pensé pour Next.js 16.
Installer next-intl et ajouter le plugin Next.js
Commencez avec un projet App Router actuel. Next.js 16 nécessite Node >=20.9.0, et les applications React actuelles utilisent React 19. Installez next-intl normalement :
npm install next-intl
Ajoutez le plugin next-intl à next.config.ts :
import createNextIntlPlugin from 'next-intl/plugin';
import type {NextConfig} from 'next';
const nextConfig: NextConfig = {};
const withNextIntl = createNextIntlPlugin();
export default withNextIntl(nextConfig);
Une petite structure de fichiers de production garde la configuration compréhensible :
src/
app/
[locale]/
layout.tsx
page.tsx
i18n/
request.ts
routing.ts
navigation.ts
proxy.ts
messages/
en.json
de.json
Créez d’abord les fichiers de messages afin que la configuration de requête ait quelque chose de réel à charger :
{
"HomePage": {
"title": "Ship your product in more languages"
},
"Metadata": {
"title": "Localized SaaS landing page",
"description": "A localized landing page built with Next.js and next-intl."
}
}
Gardez des namespaces prévisibles. Un modèle courant consiste à utiliser un namespace pour chaque route ou fonctionnalité, plus un namespace Metadata pour les titres et descriptions.
Configurer le routage basé sur la locale avec src/proxy.ts
Le routage basé sur la locale commence par une configuration de routage partagée. Placez toutes les locales prises en charge dans un seul fichier et réutilisez cette configuration partout ailleurs.
// src/i18n/routing.ts
import {defineRouting} from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en', 'de'],
defaultLocale: 'en'
});
Ensuite, créez le fichier Proxy. C’est ici que next-intl peut négocier la locale, rediriger ou réécrire les requêtes, et fournir le comportement de routage selon la locale active.
// src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)'
};
Le matcher exclut les routes API, les internes de Next.js, les internes de Vercel et les requêtes vers des fichiers avec extensions. Cela empêche le middleware de locale de s’exécuter sur des assets comme les images, les polices et les source maps.
Ajoutez maintenant la configuration de requête. C’est ici que la locale est lue et que les messages sont chargés pour la requête active.
// src/i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});
Ce fallback est important. Proxy devrait généralement fournir une locale valide, mais la configuration de requête doit tout de même gérer en sécurité une valeur invalide ou absente. La documentation next-intl recommande aussi de remplacer les valeurs [locale] invalides par une locale valide dans la configuration de requête et d’appeler notFound() dans le layout racine lorsque c’est approprié.
Construire le layout [locale] avec des params asynchrones
Dans l’App Router, votre segment de route localisé doit envelopper les pages qui ont besoin de traductions. Pour la plupart des sites marketing SaaS, cela signifie déplacer les pages publiques sous app/[locale].
Le détail important de Next.js 16 est la forme de params : la prop est une Promise, donc attendez la prop avant d’utiliser locale.
// src/app/[locale]/layout.tsx
import {NextIntlClientProvider, hasLocale} from 'next-intl';
import {setRequestLocale} from 'next-intl/server';
import {notFound} from 'next/navigation';
import {routing} from '../../i18n/routing';
type Props = {
children: React.ReactNode;
params: Promise<{locale: string}>;
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({locale}));
}
export default async function LocaleLayout({children, params}: Props) {
const {locale} = await params;
if (!hasLocale(routing.locales, locale)) {
notFound();
}
// Enables static rendering for next-intl APIs in this locale scope.
setRequestLocale(locale);
return (
<html lang={locale}>
<body>
<NextIntlClientProvider>{children}</NextIntlClientProvider>
</body>
</html>
);
}
Il y a trois tâches distinctes dans ce layout :
generateStaticParams()indique à Next.js quels paramètres de route de locale peuvent être générés statiquement.hasLocale()valide le paramètre de route avant que la page ne soit rendue.setRequestLocale(locale)rend la locale disponible aux APIs next-intl pendant le rendu statique.
Ne remplacez pas la validation de locale par une simple vérification de chaîne. La configuration de routage est la source de vérité, et hasLocale() maintient la vérification runtime alignée avec cette configuration.
Utiliser les traductions dans les Server et Client Components
next-intl prend en charge à la fois les Server Components et les Client Components, mais la surface d’API dépend de l’endroit où le composant s’exécute.
Pour les composants partagés ou serveur non asynchrones, useTranslations est valide :
// src/app/[locale]/page.tsx
import {useTranslations} from 'next-intl';
export default function HomePage() {
const t = useTranslations('HomePage');
return (
<main className='mx-auto max-w-3xl px-6 py-24'>
<h1 className='text-4xl font-semibold tracking-tight'>
{t('title')}
</h1>
</main>
);
}
Pour les Server Components asynchrones, utilisez getTranslations depuis next-intl/server :
import {getTranslations} from 'next-intl/server';
export default async function PricingPage() {
const t = await getTranslations('PricingPage');
return <h1>{t('title')}</h1>;
}
Utilisez les Client Components uniquement lorsque vous avez besoin d’état côté client, d’APIs navigateur ou de hooks interactifs. Le provider dans le layout rend les messages disponibles aux Client Components placés en dessous, mais cela ne signifie pas que chaque composant traduit doit devenir un Client Component. next-intl documente getTranslations, getMessages, getLocale et les APIs awaitables associées pour les Server Components asynchrones, tandis que des hooks comme useTranslations fonctionnent dans les composants partagés classiques selon l’endroit où ils s’exécutent.
Une règle pratique pour les landing pages est simple : gardez les sections riches en texte comme Server Components, et isolez les éléments interactifs comme les menus, les onglets et les sélecteurs de langue.
Activer le rendu statique avec generateStaticParams i18n
Le rendu statique convient généralement bien aux pages marketing, à la documentation, aux pages de pricing et aux pages SaaS publiques dont le contenu change via des déploiements ou de la revalidation plutôt qu’à chaque requête.
Pour generateStaticParams i18n, retourner les locales est nécessaire mais pas suffisant lorsque des APIs next-intl sont utilisées. Appelez setRequestLocale(locale) dans chaque layout ou page qui doit être rendu statiquement et utilise des APIs next-intl. L’appel doit se produire avant useTranslations, getMessages ou d’autres APIs next-intl qui dépendent de la locale active.
// src/app/[locale]/features/page.tsx
import {getTranslations, setRequestLocale} from 'next-intl/server';
import {routing} from '../../../i18n/routing';
type Props = {
params: Promise<{locale: string}>;
};
export function generateStaticParams() {
return routing.locales.map((locale) => ({locale}));
}
export default async function FeaturesPage({params}: Props) {
const {locale} = await params;
setRequestLocale(locale);
const t = await getTranslations('FeaturesPage');
return <h1>{t('title')}</h1>;
}
Pour le rendu statique, appelez setRequestLocale(locale) dans chaque layout ou page pertinent avant d’utiliser les APIs next-intl. Cela évite de retomber sur un rendu dynamique lorsque next-intl a besoin de la locale active.
C’est aussi ici que de nombreux bugs de mise à niveau apparaissent. Les développeurs ajoutent generateStaticParams() et observent encore un comportement dynamique parce que la locale n’a pas été définie explicitement pour le scope next-intl.
Ajouter des metadata localisées, la navigation et les vérifications hreflang
Les metadata localisées doivent utiliser le même paramètre de locale que la page. Dans les fonctions de metadata asynchrones, utilisez getTranslations et passez la locale explicitement.
// src/app/[locale]/page.tsx
import type {Metadata} from 'next';
import {getTranslations} from 'next-intl/server';
import {useTranslations} from 'next-intl';
type Props = {
params: Promise<{locale: string}>;
};
export async function generateMetadata({params}: Props): Promise<Metadata> {
const {locale} = await params;
const t = await getTranslations({locale, namespace: 'Metadata'});
return {
title: t('title'),
description: t('description'),
alternates: {
languages: {
en: '/en',
de: '/de'
}
}
};
}
export default function HomePage() {
const t = useTranslations('HomePage');
return <h1>{t('title')}</h1>;
}
Pour la navigation, évitez de construire manuellement des chaînes comme /${locale}/pricing. Exportez une fois les helpers de navigation next-intl :
// src/i18n/navigation.ts
import {createNavigation} from 'next-intl/navigation';
import {routing} from './routing';
export const {Link, redirect, usePathname, useRouter, getPathname} =
createNavigation(routing);
Puis utilisez le composant Link localisé :
import {Link} from '../../i18n/navigation';
export function Header() {
return (
<nav className='flex gap-4'>
<Link href='/'>Home</Link>
<Link href='/pricing'>Pricing</Link>
<Link href='/pricing' locale='de'>Deutsch</Link>
</nav>
);
}
Avant de publier, vérifiez trois bases SEO :
- Chaque page localisée doit avoir une URL claire, comme
/en/pricingou/de/pricing. - La valeur
<html lang>doit correspondre à la locale active. - Les liens de langue alternative doivent pointer vers un contenu équivalent, pas seulement vers la page d’accueil localisée.
Si vous associez cela à une landing page à forte conversion, les mêmes principes s’appliquent à la structure et à la performance. L’article Aniq UI sur l’optimisation des performances Next.js est un complément utile lorsque les pages localisées commencent à grossir.
Faire évoluer next-intl pour les applications plus grandes
La configuration ci-dessus convient aux blogs et aux landing pages. Mais à mesure que votre application grandit, un seul messages/en.json peut devenir un goulot d’étranglement pour l’expérience développeur, et si vous transmettez l’objet de messages complet aux Client Components, cela augmente aussi le payload client sérialisé envoyé au navigateur. Votre page d’accueil ne devrait pas sérialiser les messages de AboutPage ou CheckoutPage si cette route ne les utilise jamais.
Découper vos messages en fichiers par namespace aide l’organisation, mais cela ne corrige pas à lui seul la fuite : si vous fusionnez à nouveau chaque fichier dans request.ts, le catalogue complet se charge toujours côté serveur. La véritable correction consiste à utiliser des messages de provider sélectifs et à garder le texte des pages dans des Server Components.
Dans next-intl v4, NextIntlClientProvider hérite par défaut de la configuration de request.ts, donc si votre configuration de requête charge le catalogue complet, le provider peut exposer tout l’objet de messages aux Client Components sauf si vous vous en désengagez. Définissez messages={null} à la racine. Les Server Components continuent de fonctionner, et aucun message n’est transmis automatiquement au client :
// app/[locale]/layout.tsx
// Keep hasLocale() validation and setRequestLocale() as shown earlier
<NextIntlClientProvider messages={null}>
{children}
</NextIntlClientProvider>
Ensuite, enveloppez uniquement les îlots client qui ont besoin de traductions, en ne transmettant que leurs namespaces :
import pick from 'lodash/pick';
import {NextIntlClientProvider} from 'next-intl';
import {getMessages} from 'next-intl/server';
export default async function HomePage() {
const messages = await getMessages();
return (
<>
<Hero /> {/* Server Component, no client message payload */}
<NextIntlClientProvider
messages={pick(messages, ['Common', 'PricingCalculator'])}
>
<PricingCalculator />
</NextIntlClientProvider>
</>
);
}
Le provider doit inclure chaque namespace utilisé par le Client Component. Les providers imbriqués ne fusionnent pas les messages en profondeur, donc si PricingCalculator appelle aussi useTranslations('Common'), sélectionnez à la fois Common et PricingCalculator. La documentation next-intl décrit les props du provider comme atomiques et précise que les messages doivent être fusionnés manuellement lorsque c’est nécessaire.
Une mise en garde : lorsque vous sélectionnez un sous-ensemble, TypeScript peut signaler la prop messages parce que le type augmenté AppConfig['Messages'] attend la forme complète. C’est une aspérité connue de v4, pas une erreur dans votre configuration.
Gardez l’UI partagée dans de petits namespaces comme Common, Navigation et Footer, et gardez le contenu spécifique à une route près de sa route. Vous n’en avez pas besoin pour un petit site, mais pour les dashboards SaaS et les grandes applications multilingues, cela garde les payloads client intentionnels et les fichiers de traduction maintenables.
Pièges courants
- Utiliser
middleware.tsdans une nouvelle application Next.js 16 → la couche de requête peut ne pas correspondre à la documentation actuelle → créezsrc/proxy.tset exportezcreateMiddleware(routing). - Typer
paramscomme{locale: string}→ le layout ne correspond plus à la forme actuelle des props de l’App Router → typez-le commePromise<{locale: string}>et attendez-le. - Omettre
hasLocale()→ les segments de locale invalides peuvent rendre un contenu inattendu → validez avecrouting.localeset appeleznotFound(). - Ajouter seulement
generateStaticParams()→ next-intl peut encore avoir besoin que la locale soit définie dans le scope de rendu → appelezsetRequestLocale(locale)avant les APIs de traduction. - Construire les URLs de langue à la main → les liens peuvent casser lorsque les règles de routage changent → utilisez les helpers
createNavigation(routing). - Transmettre tous les messages au provider client racine dans une grande application → les Client Components peuvent recevoir des namespaces qu’ils n’utilisent jamais → définissez
messages={null}à la racine et transmettez des namespaces sélectionnés aux îlots client.
FAQ
L’App Router de Next.js a-t-il une prise en charge i18n intégrée ?
L’App Router de Next.js fournit des briques i18n, pas un framework de traduction complet. Il prend en charge des modèles comme la négociation basée sur Proxy, les segments dynamiques [locale] et generateStaticParams() pour les routes de locale. Des bibliothèques comme next-intl ajoutent le chargement des messages, les APIs de traduction, la navigation localisée et la gestion des langues alternatives.
Comment activer le rendu statique avec i18n dans l’App Router ?
Activez les routes i18n statiques en retournant chaque locale depuis generateStaticParams() et en définissant la locale active avant l’exécution des APIs de traduction. Avec next-intl, cela signifie généralement appeler setRequestLocale(locale) dans le layout ou la page [locale] pertinent avant useTranslations, getMessages ou des APIs similaires.
Comment fonctionnent les balises hreflang avec next-intl ?
next-intl peut aider à produire des liens de langue alternative via son middleware de routage et sa configuration de navigation localisée. Pour le SEO, chaque URL alternative doit représenter le même contenu dans une autre langue, comme /en/pricing et /de/pricing. La page doit aussi définir la bonne valeur <html lang>.
Comment corriger “Unable to find next-intl locale because the middleware didn’t run on this request” ?
Cette erreur signifie généralement que la négociation de requête next-intl ne s’est pas exécutée pour la route en cours de rendu. Dans Next.js 16, confirmez que src/proxy.ts existe, exporte createMiddleware(routing) et possède un matcher qui inclut la page localisée. Confirmez aussi que la page se trouve sous app/[locale] lorsque vous utilisez un routage basé sur la locale.
Pourquoi Next.js 16 a-t-il renommé middleware.ts en proxy.ts ?
Next.js 16 utilise la convention de fichier proxy.ts pour la couche d’interception de requêtes que de nombreux anciens guides appelaient middleware.ts. La documentation indique que Middleware a été renommé Proxy pour mieux refléter son rôle, et que la fonctionnalité reste la même. Dans les nouvelles applications, placez l’export du middleware next-intl dans src/proxy.ts et évitez de mélanger les deux noms dans un même tutoriel ou une même arborescence de fichiers.
Comment configurer next-intl Next.js 16 pour une landing page SaaS ?
Configurez le routage par locale avec defineRouting, créez src/proxy.ts avec createMiddleware(routing), enveloppez les pages dans app/[locale]/layout.tsx et chargez les messages via i18n/request.ts. Pour une landing page SaaS, ajoutez aussi des metadata localisées, validez les locales et utilisez les helpers de navigation next-intl pour les liens de langue.
Faut-il découper les messages next-intl par route ?
Pour les petits sites, un seul fichier de messages par locale convient. Pour les applications plus grandes, découpez les messages par route ou fonctionnalité pour l’expérience développeur, mais ne fusionnez pas le catalogue complet pour le transmettre à chaque Client Component. Gardez la majorité du contenu traduit dans des Server Components, définissez messages={null} sur le provider racine et ne transmettez que les namespaces sélectionnés aux îlots client qui en ont besoin.
Conclusion
Une configuration App Router multilingue fiable repose sur quelques choix précis : utilisez src/proxy.ts, attendez et validez les params de locale, gardez la configuration de routage centralisée, et associez generateStaticParams() à setRequestLocale() lorsque le rendu statique compte. Une fois ces éléments en place, les metadata et les liens de langue deviennent beaucoup plus faciles à raisonner.
Pour les applications SaaS plus grandes, l’étape suivante est la discipline des payloads : gardez le texte des pages sur le serveur, désactivez les messages client automatiques à la racine, et ne transmettez que les namespaces dont vos îlots interactifs ont besoin. Si vous localisez une landing page SaaS ou une UI de dashboard, partir d’un template Aniq UI peut vous faire gagner du temps sur le layout environnant pendant que vous vous concentrez sur la couche i18n.
Cet article vous a été utile?
Vous pourriez aussi aimer

next-intl nextjs 16: cache de composants via root-params
Maîtrisez l'intégration de next-intl avec nextjs 16 grâce à next/root-params pour activer en toute sécurité le cache de composants sans prop-drilling de la locale.

Modale Next.js Intercepting Routes : Formulaires Dashboard SaaS
Créez des modales Next.js avec intercepting routes, liens profonds et préservation d'état via parallel slots, Server Actions et shadcn/ui pour vos dashboards SaaS.

Next.js View Transitions : Animations Natives Zéro JS
Remplacez les bibliothèques d'animation lourdes par les Next.js view transitions pour créer des animations fluides et sans JS nativement.