

Next.js dispose de deux pages de documentation officielles sur l'internationalisation (i18n) qui décrivent des systèmes totalement différents, sans aucun lien entre elles.
L'un traite du routeur de pages. L'autre traite du routeur d'applications. Aucun des deux n'explique comment ces deux éléments s'articulent, quelles bibliothèques utiliser, ni comment gérer les balises hreflang pour SEO multilingue.
Nous allons relier les deux routeurs. Nous aborderons les stratégies de routage, le choix des bibliothèques, la configuration des intergiciels, la traduction des composants serveur et SEO multilingue.
Notre objectif est de créer une ressource utile pour les développeurs à qui l'on a demandé d'ajouter la prise en charge d'une nouvelle langue à une application Next.js et qui ont besoin d'y voir plus clair avant de s'engager dans une approche. C'est parti !
L'internationalisation comporte trois niveaux :
Next.js ne sert qu'au routage.
Le routeur Pages intègre une fonctionnalité de routage i18n depuis la version 10.0.0. Un bloc de configuration à trois champs dans le fichier next.config.js gère automatiquement la détection de la locale, l'ajout de préfixes aux URL et les redirections.
L'App Router est différent. Sa page de documentation sur l'internationalisation (i18n) décrit un modèle que vous assemblez vous-même à l'aide de middleware, un [langue] un segment dynamique et une bibliothèque.
En ce qui concerne les traductions et la mise en forme, vous devrez vous débrouiller seul, quel que soit le routeur que vous utilisez. La documentation d'App Router répertorie toutes les bibliothèques compatibles, mais ne donne aucune indication sur le choix à faire. Aucune de ces pages de documentation ne traite des balises hreflang ni des plans de site multilingues.
L'approche du routeur de pages consiste à modifier la configuration. Ajoutez un bloc i18n au fichier next.config.js avec trois champs :
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'fr', 'de'],
defaultLocale: 'en',
},
}C'est tout.
Next.js détecte automatiquement les paramètres régionaux à partir du Accept-Language en-tête, respecte un NEXT_LOCALE remplace le cookie et indique la configuration régionale active via useRouter().locale. Vous n'avez besoin ni de middleware ni de restructuration des répertoires.
L'App Router ne dispose d'aucune configuration équivalente. Vous devez définir vous-même le routage à partir de trois éléments :
@formatjs/intl-localematcher et négociateur à analyser Accept-Language en-têtes. La documentation officielle laisse la logique de détection proprement dite en suspens : function getLocale(request) { ... }. Nous aborderons ce point dans la section suivante.[langue] Le segment dynamique englobe l'ensemble de votre app/ répertoire. Chaque route prend en compte les paramètres régionaux grâce à la structure des dossiers.générerParamètresStatiques prérenderise chaque variante de langue lors de la compilation.Parmi ces trois éléments, c'est generateStaticParams qui remplace ce que le routeur Pages gérait automatiquement lors de la compilation.
export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'fr' }, { locale: 'de' }]
}🚨 La fonctionnalité d'internationalisation (i18n) intégrée au routeur Pages ne fonctionne pas avec sortie : « export ». Si vous avez besoin d'une exportation entièrement statique, vous devrez plutôt recourir à un routage manuel basé sur des dossiers. Cette limitation s'applique uniquement à la configuration intégrée. Les deux routeurs prennent en charge les exportations statiques avec une configuration manuelle.
Avant de choisir une bibliothèque, déterminez une stratégie de routage. Celle-ci a une incidence sur le référencement naturel (SEO), la configuration DNS et la quantité de middleware que vous devrez développer :
next-intl le considère comme une option de premier choix.Pour les sites publics où le référencement naturel (SEO) est essentiel, le routage par sous-chemin est le choix par défaut recommandé. Le routage par domaine ne justifie sa complexité que si des signaux de ciblage géographique forts constituent une exigence spécifique.
Si vous préférez ne pas gérer manuellement la structure des URL, le proxy inverseWeglot gère automatiquement le routage des sous-répertoires et des sous-domaines, y compris l'insertion d'attributs hreflang et les URL traduites.
Comme nous l'avons déjà mentionné, la documentation officielle répertorie les bibliothèques sans en recommander aucune. Voici un comparatif des principales options :
next-intl est la norme de facto pour l'App Router. La prise en charge de RSC est intégrée, le routage est intégré et la saisie semi-automatique de TypeScript fonctionne sans configuration supplémentaire.
react-i18next est la solution la plus adaptée si vous utilisez le routeur Pages ou si vous utilisez déjà i18next ailleurs dans votre pile. La prise en charge du routeur App fonctionne, mais nécessite davantage de configuration manuelle.
Lingui convient aux équipes qui disposent déjà de pipelines de fichiers .po. Les traductions sont extraites lors de la compilation, de sorte que les messages inutilisés ne sont jamais inclus dans la version finale.
💡 Paraglide JS ne figure pas vraiment parmi les principaux acteurs, mais il mérite d'être mentionné en tant qu'alternative basée sur un compilateur offrant des paquets de sortie plus légers. Si cela vous intéresse, vous devrez toutefois composer avec le fait que son écosystème est moins développé que celui des autres.
Pour le formatage des dates et des nombres, next-intl encapsule les API Intl natives via useFormatter. Les autres bibliothèques délèguent cette tâche à FormatJS ou vous laissent le soin d'appeler directement Intl.DateTimeFormat et Intl.NumberFormat.
💡 Ces bibliothèques gèrent l'affichage. Il faut toutefois que quelqu'un se charge de produire les traductions et de les maintenir à jour à mesure que le contenu évolue. Weglot à un tout autre niveau : il gère le contenu traduit via un proxy inverse, en dehors du code source. Ne le considérez pas comme un simple substitut à ces bibliothèques.
Dans Next.js 16, le fichier « middleware.ts » a été renommé « proxy.ts ». Ce changement a été effectué car le terme « middleware » prêtait à confusion et suggérait une logique générique au sein de l'application. En réalité, ce fichier s'exécute au niveau de la frontière réseau, où il intercepte et modifie les requêtes à la manière d'un proxy avant qu'elles n'atteignent l'application.
Voici une implémentation complète de la détection de la langue et de la redirection qui comble les lacunes mentionnées plus haut dans la documentation officielle :
// proxy.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'
import { NextRequest, NextResponse } from 'next/server'
const locales = ['en', 'fr', 'de']
const defaultLocale = 'en'
function getLocale(request: NextRequest): string {
const cookie = request.cookies.get('NEXT_LOCALE')?.value
if (cookie && locales.includes(cookie)) return cookie
const headers = { 'accept-language': request.headers.get('accept-language') ?? '' }
const languages = new Negotiator({ headers }).languages()
return match(languages, locales, defaultLocale)
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const hasLocale = locales.some(l => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)
if (hasLocale) return
const locale = getLocale(request)
request.nextUrl.pathname = `/${locale}${pathname}`
return NextResponse.redirect(request.nextUrl)
}
Les fichiers sont imbriqués sous app/[langue]/, avec un dictionnaires/ dossier à côté de vos mises en page et de vos pages. La mise en page racine reçoit paramètres régionaux en tant que paramètre et le définit sur le html balise :
export default async function RootLayout({ children, params }) {
const { locale } = await params
return (
<html lang={locale}>
<body>{children}</body>
</html>
)
}
Pour les traductions, la documentation officielle présente un modèle de dictionnaire brut qui ne nécessite aucune bibliothèque :
const dictionaries = {
en: () => import('./dictionaries/en.json').then(m => m.default),
fr: () => import('./dictionaries/fr.json').then(m => m.default),
}
export const getDictionary = async (locale: string) => dictionaries[locale]()
Cela fonctionne, mais ne prend pas en charge la pluralisation ni l'interpolation. next-intl couvre les deux via getTranslations() et useTranslations() dans Composants serveur et composants client respectivement.
Dans tous les cas, les fichiers de traduction sont traités sur le serveur et seul le code HTML généré est envoyé au navigateur ; la taille des fichiers de messages n'a donc aucune incidence sur la taille du paquet client.
Pour les composants client, transmettez les chaînes traduites sous forme de props depuis un composant serveur parent, dans la mesure du possible. Utilisez NextIntlClientProvider avec un sous-ensemble de messages délimité uniquement lorsqu'un composant client doit gérer directement les traductions.
Dans le cas d'un sélecteur de langue, un composant serveur affiche les libellés de paramètres régionaux tandis qu'un composant client gère la useRouter() appel :
// LocaleSwitcher.tsx (Server Component)
import LocaleSwitcherClient from './LocaleSwitcherClient'
export default function LocaleSwitcher({ locale }) {
return <LocaleSwitcherClient locale={locale} labels={{ en: 'English', fr: 'Français' }} />
}Le client surveille les changements de sélection et transmet la nouvelle route de localisation :
// LocaleSwitcherClient.tsx
'use client'
import { useRouter } from 'next/navigation'
export default function LocaleSwitcherClient({ locale, labels }) {
const router = useRouter()
return (
<select value={locale} onChange={e => router.push(`/${e.target.value}`)}>
{Object.entries(labels).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
)
}Si vous utilisez Weglot, rien de tout cela ne s'applique. Weglot un widget de changement de langue et ne nécessite aucune configuration de proxy ni aucun intergiciel.
Aucun des deux routeurs ne gère SEO multilingue :
html lang correctement, mais sa documentation indique que la mise en œuvre de hreflang est « à votre discrétion ».Grâce à l'App Router, l'API Metadata vous permet de vous rapprocher au plus près de l'automatisation. Définissez des URL de paramètres régionaux dans générer des métadonnées par l'intermédiaire de langues alternatives objet et Next.js procède à l'injection automatique link rel="alternate" hreflang="..." tags :
export async function generateMetadata({ params: { locale } }) {
return {
alternates: {
languages: {
'en': 'https://example.com/en',
'fr': 'https://example.com/fr',
'de': 'https://example.com/de',
},
},
}
}La partie manuelle consiste à récupérer l'URL correcte pour chaque variante linguistique de chaque page. C'est à vous de mettre en place cette logique.
Deux autres éléments doivent être configurés manuellement, quel que soit le routeur utilisé. Les URL canoniques doivent être définies pour chaque locale afin d'éviter les signaux de contenu dupliqué. Votre sitemap.ts besoins xhtml:link des entrées pour chaque variante linguistique de chaque page.
Sur le hreflang la valeur elle-même, utiliser hreflang="en-US" lorsqu'on cible une région spécifique, et hreflang="fr" lorsqu'on s'adresse à l'ensemble des anglophones. Confondre ces deux éléments envoie des signaux contradictoires aux moteurs de recherche.
next-intl ajoute une automatisation partielle : son proxy insère automatiquement l'attribut hreflang Lien en-têtes de réponse lorsque vous utilisez le routage par chemin d'accès localisé. Les autres configurations de routage nécessitent toujours une mise en œuvre manuelle.
Cela représente une charge de maintenance non négligeable à mesure que votre site se développe. Heureusement, le proxy inverse Weglot gère tout automatiquement au niveau du serveur, ce qui permet aux pages traduites d'être entièrement indexables sans code supplémentaire.
En réalité, le proxy inverse Weglot gère automatiquement tous les éléments abordés dans la section précédente : balises hreflang, URL traduites, balises canoniques et génération de plans de site. Les pages traduites sont générées côté serveur, ce qui les rend entièrement indexables par les moteurs de recherche.
⚠️ snippet JavaScript Weglot n'apporte aucun avantage en matière de référencement naturel (SEO). La traduction côté client n'est pas indexable par les moteurs de recherche. Si le référencement naturel est important, la configuration d'un proxy inverse est nécessaire.
Un autre avantage réside dans le temps de compilation. Weglot les pages traduites via son propre CDN plutôt que de les générer au moment de la compilation. L'ajout de 10 langues à un site Weglot n'a aucune incidence sur la durée prochaine version il faut.
Le choix qui s'impose dépend de l'objectif que vous poursuivez.
Si vous développez sur l'App Router et que vous partez de zéro, next-intl C'est le choix par défaut. Il regroupe en un seul paquet le routage, les traductions, la saisie semi-automatique TypeScript et la prise en charge de RSC.
Si vous utilisez Pages Router ou si i18next fait déjà partie de votre pile, react-i18next c'est la solution la plus facile.
Si SEO multilingue la gestion continue des traductions sont vos priorités, aucune de ces deux bibliothèques ne résout entièrement le problème. Vous devrez tout de même mettre en place des balises hreflang, des plans de site et un processus permettant de maintenir les traductions à jour à mesure que le contenu évolue. Le proxy inverse Weglot gère tout cela automatiquement.
Weglot une bibliothèque au niveau des composants ne s'excluent pas mutuellement. Weglot au niveau du code HTML et de la couche proxy, ce qui lui permet de coexister avec n'importe quelle bibliothèque utilisée par vos composants.
Si cette approche correspond à votre situation, consultez la page d'intégration JavaScript Weglot pour commencer et profitez de notre essai gratuit de 14 jours!
La meilleure façon de comprendre la puissance de Weglot de le tester par vous-même. Essayez-le gratuitement et sans engagement.
Un site web de démonstration est disponible dans votre tableau de bord si vous n'êtes pas encore prêt à connecter votre site web.