Posez votre question et obtenez un résumé du document en referencant cette page et le Provider AI de votre choix
Le contenu de cette page a été traduit à l'aide d'une IA.
Voir la dernière version du contenu original en anglaisSi vous avez une idée d’amélioration pour améliorer cette documentation, n’hésitez pas à contribuer en submitant une pull request sur GitHub.
Lien GitHub de la documentationCopier le Markdown du doc dans le presse-papiers
next-intl VS Intlayer | Benchmark d'Internationalization (i18n) Next.js
next-intl est la bibliothèque i18n la plus populaire pour Next.js. Intlayer est une alternative basée sur un compilateur et scoped au niveau des composants. Les deux localisent une application App Router. La question est ce que chacune coûte une fois que l'application est construite.
Cet article n'est pas un tutoriel. C'est une comparaison étayée par des chiffres provenant de Benchmark Bloom, une suite de benchmark open-source qui construit la même application avec chaque bibliothèque et mesure ce que le navigateur télécharge et exécute réellement.
tl;dr: Sur la même application Next.js,next-intlajoute +12.6 KB gzip de JavaScript sur chaque page, versus +0.3 KB pour Intlayer. Sans travail supplémentaire,next-intlexpédie ~90% des chaînes de pages étrangères avec chaque page. Atteindre 0% de fuite avecnext-intlnécessite la portée des namespaces etpick(messages, [...])par page. Intlayer atteint 0% par défaut, car son compilateur limite le contenu par composant. Si vous voulez l'APInext-intlavec la sortie d'Intlayer, l'adaptateur@intlayer/next-intla mesuré 147.5 KB par page versus 153.6 KB avec l'original.
En résumé
- next-intl - Léger, bien documenté, format de message ICU, support de première classe pour App Router avec middleware, formatters et helpers de navigation. Le contenu réside dans des catalogues JSON centralisés ; les optimisations de performance (namespaces, picking de messages par page, lazy loading) sont de votre responsabilité.
- Intlayer - Modèle de contenu centré sur les composants. Les dictionnaires
.content.tsse situent à côté du composant qu'ils servent, un compilateur au moment du build tree-shake et lazy-load les contenus par composant et par locale, des types TypeScript stricts sont générés à partir de votre contenu, et les traductions manquantes échouent au moment du build. Inclut middleware, helpers SEO, un Visual Editor / CMS et traduction assistée par IA.
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
Les badges se mettent à jour automatiquement. Les snapshots varieront au fil du temps.
Comparaison des fonctionnalités côte à côte
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Fonctionnalité | next-intlayer (Intlayer) | next-intl |
|---|---|---|
| Traductions près des composants | ✅ Oui, .content.ts colocalisé avec chaque composant | ❌ Non, messages/{locale}.json centralisé |
| Intégration TypeScript | ✅ Types stricts générés automatiquement à partir du contenu | ✅ Bon, clés typées via l'augmentation global.d.ts |
| Détection des traductions manquantes | ✅ Erreur TypeScript + erreur/avertissement au moment de la construction | ⚠️ Fallback au runtime + avertissement console |
| Contenu riche (JSX / Markdown / composants) | ✅ Support direct | ⚠️ t.rich() / t.markup() avec placeholders de balises |
| Support ICU | ⚠️ WIP | ✅ Oui |
| Formatage (dates, nombres, devises) | ✅ useNumber, useDate, ... (Intl sous le capot) | ✅ useFormatter() (Intl sous le capot) |
| Routage localisé & middleware | ✅ Proxy/middleware intégré, getMultilingualUrls | ✅ Middleware intégré, Link, redirect, usePathname |
| Helpers SEO (hreflang, sitemap, robots) | ✅ Helpers intégrés | ⚠️ Manuel, basé sur la configuration du routage |
| Server components synchrones | ✅ useIntlayer depuis next-intlayer/server fonctionne dans n'importe quel composant serveur enfant | ⚠️ getTranslations est async; les enfants synchrones ont besoin que t soit passé en tant que props |
| Rendu statique | ✅ Ne bloque pas le rendu statique | ⚠️ Nécessite setRequestLocale(); les catalogues avec namespace ont quand même exclu les pages du rendu statique dans nos tests |
| Tree-shaking (livrer uniquement le contenu utilisé) | ✅ Par composant, par locale, automatisé par le compilateur | ⚠️ Manuel : namespaces + pick(messages, [...]) par page |
| Chargement différé | ✅ importMode: 'dynamic' (une ligne de configuration) | ⚠️ Imports dynamiques manuels dans getRequestConfig |
| Purge unused content | ✅ Les dictionnaires inutilisés sont supprimés au moment de la compilation | ❌ Non intégré |
| Testing missing translations (CLI / CI) | ✅ npx intlayer content test | ⚠️ Non intégré; la documentation suggère npx @lingual/i18n-check |
| AI-powered translation | ✅ Intégré, utilise vos propres clés de fournisseur | ❌ Non |
| Éditeur visuel / CMS | ✅ Éditeur visuel gratuit + CMS optionnel | ❌ Non (plateformes de localisation externes) |
| Serveur MCP & Agent Skills | ✅ Oui | ❌ Non |
| Écosystème / communauté | ⚠️ Plus petit mais croissance rapide | ✅ Large, la référence Next.js |
Le benchmark
Ce qui a été mesuré
La suite Benchmark Bloom construit la même application avec chaque bibliothèque : 10 pages (accueil, à propos, blog, carrières, contact, FAQ, tarification, produits, paramètres, équipe), 10 locales (en, fr, es, de, it, pt, zh, ja, ko, ru), composants identiques et contenu identique. Les pages sont mesurées en en et fr. Chaque bibliothèque est implémentée dans jusqu'à quatre stratégies de chargement, de la configuration naïve à la configuration optimale :
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Stratégie | Description | Qui fait cela |
|---|---|---|
| static | Chaque locale et chaque page regroupées ensemble | Prototypes rapides, code généré par IA |
| dynamic | Seule la locale active est chargée, mais toutes les pages à la fois | La plupart des projets |
| scoped-static | Espaces de noms par route, pas de chargement lazy | Rare |
| scoped-dynamic | Espaces de noms par route + chargement lazy. Seule la page actuelle dans la locale actuelle est envoyée | Applications avec un budget de performance strict |
Intlayer n'a pas de variante "scoped" : le compilateur scopes le contenu par composant automatiquement, donc ses lignes static et dynamic sont déjà scoped.
Pour chaque build, la suite enregistre :
- Lib size : taille gzip de un composant vide qui importe uniquement la bibliothèque i18n. Le coût fixe du runtime.
- Page JS : JavaScript gzip téléchargé par page, moyenné sur toutes les pages et locales.
- Locale leak % : part des chaînes traduites trouvées dans le JS téléchargé qui appartiennent à une locale que l'utilisateur n'est pas en train de consulter (fingerprint sur
enetfr, donc 50% signifie « l'autre locale mesurée est entièrement présente » ; avec 10 locales bundlées, le vrai gaspillage est plus élevé). - Page leak % : part des chaînes traduites trouvées dans le JS téléchargé qui appartiennent à une page sur laquelle l'utilisateur n'est pas.
- Component avg : taille gzip moyenne de chaque composant compilé isolément. Montre combien de runtime i18n un seul composant entraîne.
- E2E reactivity: temps écoulé entre la sélection d'une nouvelle locale et la mise à jour de
html[lang]dans le DOM (Playwright, 5 itérations). - Hydration: durée de la phase d'hydratation de React.
Les chiffres ci-dessous proviennent de l'exécution datée du 2026-09-12 avecnext-intl4.14.2,use-intl4.14.2 etintlayer9.5.1. L'application de test est volontairement petite (quelques dizaines de chaînes par locale), donc les pourcentages de fuite décrivent un pattern : ils augmentent avec votre contenu tandis que le coût d'exécution reste fixe.
Résultats sur Next.js (App Router)
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (sans i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
next-intlayer | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
@intlayer/next-intl (compat) | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl (compat) | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
Comment le lire
- Coût du runtime. L'application de base pèse 141.0 KB par page.
next-intlla porte à 153.6 KB (+12.6 KB gzip sur chaque page), Intlayer à 141.3 KB (+0.3 KB). Cet écart ne dépend pas du nombre de chaînes que vous avez : c'est le runtime de la librairie. - Leakage. Dans les deux configurations que la plupart des équipes déploient réellement (
staticetdynamic),next-intllivre ~90% des chaînes de pages étrangères avec chaque page : l'ensemble duen.jsonest inséré dans le provider client. Pour atteindre 0%, il faut utiliser les configurationsscoped-*: diviser les catalogues en namespaces, puis utiliserpick()pour sélectionner les bons dans chaque page. Intlayer est à 0% dans les deux lignes sans rien de tout cela. - Le JS par page n'a pas changé pour
next-intlentre les stratégies. Le contenu du test est petit, donc la fuite ~90% ne représente que quelques KB ici. Sur une vraie application avec des centaines de chaînes par page, ce ratio devient le coût dominant. Pendant ce temps, le runtime +12.6 KB est payé dans chaque configuration. - Taille des composants. Un composant qui appelle
useTranslations()compile à 21.8 KB en moyenne ; le même composant avecuseIntlayer()compile à 6.9 KB. Dans la configurationscoped-static, les composantsnext-intlsautent à 80.1 KB car chacun intègre son catalogue de namespaces. - Réactivité et hydratation sont dans le même ordre de grandeur pour les deux bibliothèques sur Next.js (15-18 ms). Aucune n'est un goulot d'étranglement ici.
Résultats sur TanStack Start (use-intl)
use-intl est le cœur indépendant du framework de next-intl. Même API, même format de message. Le comparer à intlayer sur TanStack Start supprime les parties spécifiques à Next.js de l'équation.
Ouvrir le tableau dans une fenêtre modale pour voir tout le contenu clairement
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity |
|---|---|---|---|---|---|---|---|
| base (pas d'i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms |
intlayer | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms |
intlayer | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms |
@intlayer/use-intl (compat) | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms |
Comment le lire
- La configuration naive
use-intlembarque 68.8 KB de JS supplémentaires par page par rapport à l'application de base, avec la moitié des chaînes appartenant à la mauvaise locale et 90% à la mauvaise page. use-intlen modedynamicatteint 119.4 KB, proche des 118.6 KB d'Intlayer, mais porte toujours 89.8% de fuite de page : toutes les chaînes de la locale active pour toutes les pages sont chargées sur chaque page. Les délimiter par route (scoped-*) supprime la fuite mais coûte un autre ~9 KB de surcharge de chunk.- La ligne
staticd'Intlayer a déjà 0% de fuite de page : le compilateur n'agrège que les dictionnaires utilisés par les composants de la page. L'activation deimportMode: 'dynamic'(une ligne dansintlayer.config.ts) supprime aussi la fuite de locale. - La taille du composant montre où se trouve l'architecture : 76-87 KB par composant avec
use-intlversus 6-8 KB avec Intlayer.useTranslations()lie chaque composant à l'arborescence de messages globale ;useIntlayer()le lie à son propre dictionnaire. - Le changement de locale est 2 à 4 fois plus rapide avec Intlayer (3 ms contre 7-21 ms).
Pourquoi cet écart ? Catalogues centralisés vs dictionnaires compilés
next-intl suit le modèle classique : un JSON par locale, chargé dans getRequestConfig, poussé dans un NextIntlClientProvider, lu via t("namespace.key").
Copier le code dans le presse-papiers
Le runtime ne peut pas connaître les clés qu'une page utilisera, donc la valeur par défaut sûre est d'envoyer l'ensemble du catalogue. Optimiser signifie que vous divisez le catalogue en namespaces, que vous décidez quels namespaces chaque page a besoin, et que vous mainteniez cette correspondance synchronisée lorsque les composants se déplacent. La ligne scoped-dynamic du benchmark est la récompense pour ce travail, et la plupart des équipes n'y arrivent jamais.
Intlayer inverse la responsabilité. Le contenu est déclaré à côté du composant :
Copier le code dans le presse-papiers
Au moment de la compilation, le compilateur (@intlayer/swc / @intlayer/babel) voit quel composant importe quel dictionnaire. Il regroupe uniquement ces dictionnaires, uniquement pour la locale active, et supprime ceux qu'aucun composant n'importe. Le pattern "scoped-dynamic" devient le résultat de la compilation au lieu d'une discipline que l'équipe doit maintenir.
Pour obtenir les chiffres de la lignedynamic, définissezdictionary.importMode: 'dynamic'dansintlayer.config.ts. Consultez la documentation sur l'optimisation du bundle.
Expérience développeur
Composant client
next-intl
Copier le code dans le presse-papiers
Copier le code dans le presse-papiers
N'oubliez pas d'inclure l'espace de nomscounterdans les messages passés àNextIntlClientProvidersur chaque page qui rend ce composant.
Intlayer
Copier le code dans le presse-papiers
Copier le code dans le presse-papiers
Rien à enregistrer sur la page : le composant apporte son propre contenu.
Composant serveur synchrone
Les éléments du design system (navbar, footer, cards) sont souvent des composants serveur rendus comme enfants de composants clients, ils ne peuvent donc pas être async.
next-intl
Copier le code dans le presse-papiers
La page doit await getTranslations("counter") et await getFormatter(), puis transmettre les résultats en tant que props. Le composant n'est plus autonome.
Intlayer
Copier le code dans le presse-papiers
Métadonnées
next-intl
Copier le code dans le presse-papiers
Intlayer
Copier le code dans le presse-papiers
Conservez l'API next-intl, obtenez la sortie d'Intlayer
Vous n'avez pas besoin de réécrire les composants pour obtenir les chiffres de benchmark ci-dessus. @intlayer/next-intl est un adaptateur clé en main : il conserve useTranslations, getTranslations, useFormatter, t.rich(), les pluriels ICU et les helpers next-intl/navigation, et les sert à partir des dictionnaires Intlayer compilés par le compilateur Intlayer.
Copier le code dans le presse-papiers
Dans le benchmark, la build de compatibilité de la même application est passée de 153,6 KB à 147,5 KB par page, de 21,8 KB à 8,1 KB par composant, et de ~90% de fuite de page à 0%, sans modifier le code de l'application. Vos fichiers messages/{locale}.json existants peuvent rester la source de vérité grâce au plugin JSON sync.
Consultez le guide de migration next-intl pour les étapes détaillées.
Quand choisir lequel ?
- Choisissez next-intl si vous voulez le standard de l'écosystème pour Next.js, vous dépendez d'ICU MessageFormat, votre application est de petite à moyenne taille, ou vous intégrez avec une plateforme de traduction (Crowdin, Phrase, Lokalise...) qui attend du JSON centralisé. Prévoyez du temps pour structurer les catalogues par namespace et sélectionner les messages par page si la performance compte.
- Choisissez Intlayer si vous voulez du contenu scoped au composant, du TypeScript strict, des erreurs de clés manquantes à la compilation, du tree-shaking et lazy loading sans effort, des composants serveur synchrones, et des outils éditoriaux intégrés (Visual Editor, CMS, traduction IA, serveur MCP). Particulièrement pertinent pour les codebases volumineuses et modulaires et les design systems.
- Choisissez
@intlayer/next-intlsi vous êtes déjà surnext-intlet voulez les gains de bundle sans réécriture complète.
Comparaisons connexes
- i18next vs Intlayer (même benchmark)
- Lingui vs Intlayer (même benchmark)
- vue-i18n vs Intlayer benchmark (même benchmark)
- next-i18next vs next-intl vs Intlayer
- Is next-intl outdated?
GitHub STARs
Les stars GitHub sont un indicateur fort de la popularité d'un projet, de la confiance de la communauté et de sa pertinence à long terme. Bien que ce ne soit pas une mesure directe de la qualité technique, ils reflètent le nombre de développeurs qui trouvent le projet utile, suivent sa progression et sont susceptibles de l'adopter.
Conclusion
next-intl est une bibliothèque solide et bien maintenue, et le benchmark confirme qu'elle est loin d'être la pire option sur Next.js. Cependant, son modèle de catalogue centralisé place chaque optimisation sur les épaules du développeur : la configuration naïve fuit environ 90% du contenu de pages étrangères, et le runtime seul coûte +12,6 KB gzip sur chaque page.
Intlayer déplace ce travail vers le compilateur. Les dictionnaires par composant, le lazy loading par locale et la purge du contenu mort sont des sorties de build, pas des conventions. Le résultat sur la même application : +0.3 KB par page, 0% de fuite, des composants 3x plus petits, et un changement de locale 2x-4x plus rapide sur TanStack Start.
Toutes les données brutes, les applications de test et les scripts se trouvent dans le repository Benchmark Bloom. Exécutez-le vous-même.
Consultez la documentation 'Why Intlayer?' pour plus de détails.
Commentaires
Aucun commentaire pour le moment. Soyez le premier à partager vos pensées.
