Stellen Sie Ihre Frage und erhalten Sie einen Resümee des Dokuments, indem Sie diese Seite und den AI-Anbieter Ihrer Wahl referenzieren
Versionshistorie
- "GitHub-Sterne-Vergleich hinzufügen"v8.9.818.5.2026
- "Benchmark initialisiert"v8.7.56.1.2026
Der Inhalt dieser Seite wurde mit einer KI übersetzt.
Den englischen Originaltext ansehenIf you have an idea for improving this documentation, please feel free to contribute by submitting a pull request on GitHub.
GitHub link to the documentationCopy doc Markdown to clipboard
Next.js i18n-Bibliotheken - Benchmark-Bericht 2026
Diese Seite ist ein Benchmark-Bericht für i18n-Lösungen auf Next.js.
Inhaltsverzeichnis
Interaktiver Benchmark
Ergebnis-Referenz:
Vollständige Benchmark-Daten anzeigen
Das vollständige Benchmark-Repository finden Sie hier.
Einführung
Internationalisierungs-Bibliotheken haben einen starken Einfluss auf Ihre Anwendung. Das Hauptrisiko besteht darin, Inhalte für jede Seite und jede Sprache zu laden, wenn der Benutzer nur eine einzige Seite besucht.
Wenn Ihre App wächst, kann die Bundle-Größe exponentiell zunehmen, was die Performance spürbar beeinträchtigen kann.
Beispielsweise kann Ihre Seite nach der Internationalisierung bei den schlimmsten Beispielen fast viermal größer sein.
Ein weiterer Effekt von i18n-Bibliotheken ist die langsamere Entwicklung. Die Umwandlung von Komponenten in mehrsprachige Inhalte über verschiedene Sprachen hinweg ist zeitaufwendig.
Da das Problem komplex ist, gibt es viele Lösungen – einige konzentrieren sich auf die DX (Developer Experience), andere auf Performance oder Skalierbarkeit usw.
Intlayer versucht, über all diese Dimensionen hinweg zu optimieren.
TL;DR
- Intlayer & next-translate: Top-Empfehlungen für die Next.js-Performance, mit dem kleinsten Fußabdruck und der besten Unterstützung für statisches Rendering.
- next-intl: Die aktuell trendigste Option, aber schwerfällig und komplex für die Optimierung großer Anwendungen.
- next-i18next: Beliebt und reich an Plugins, bringt jedoch ein erhebliches Bundle-Gewicht mit sich (~3× Intlayer).
- Vermeiden: gt-next und lingo.dev aufgrund schwerwiegender Performance-Probleme, Vendor-Lock-in und Build-Fehlern.
Testen Sie Ihre App
Um diese Probleme aufzudecken, habe ich einen kostenlosen Scanner entwickelt, den Sie hier ausprobieren können.
Das Problem
Es gibt zwei wesentliche Möglichkeiten, die Auswirkungen einer mehrsprachigen App auf Ihr Bundle zu begrenzen:
- Aufteilung Ihres JSONs (oder Inhalts) auf Dateien / Variablen / Namespaces, damit der Bundler ungenutzte Inhalte für eine bestimmte Seite herausfiltern (Tree-shaking) kann.
- Dynamisches Laden Ihrer Seiteninhalte nur in der Sprache des Benutzers.
Technische Einschränkungen für diese Ansätze:
Dynamisches Laden
Auch wenn Sie Routen wie [locale]/page.tsx deklarieren, behandeln Webpack oder Turbopack locale nicht als statische Konstante, selbst wenn generateStaticParams definiert ist. Das bedeutet, dass Inhalte für alle Sprachen in jede Seite gezogen werden können. Der Hauptweg, dies zu begrenzen, besteht darin, Inhalte über einen dynamischen Import zu laden (z. B. import('./locales/${locale}.json')).
Beim Build-Vorgang erzeugt Next.js ein JS-Bundle pro Sprache (z. B. ./locales_fr_12345.js). Wenn die Seite im Client ausgeführt wird, stellt der Browser eine zusätzliche HTTP-Anfrage für die benötigte JS-Datei (z. B. ./locales_fr_12345.js).
Ein anderer Weg, dasselbe Problem zu lösen, ist die Verwendung vonfetch(), um JSON dynamisch zu laden. So arbeitetTolgee, wenn JSON unter/publicliegt, odernext-translate, das sich aufgetStaticPropszum Laden von Inhalten verlässt. Der Ablauf ist derselbe: Der Browser stellt eine zusätzliche HTTP-Anfrage, um das Asset zu laden.
Content-Splitting (Inhaltsaufteilung)
Wenn Sie eine Syntax wie const t = useTranslation() + t('mein-objekt.mein-unterobjekt.mein-schluessel') verwenden, muss normalerweise das gesamte JSON im Bundle enthalten sein, damit die Bibliothek den Schlüssel auflösen kann. Ein Großteil dieses Inhalts wird also mitgeliefert, auch wenn er auf der Seite nicht verwendet wird.
Um dies abzumildern, verlangen einige Bibliotheken, dass Sie pro Seite deklarieren, welche Namespaces geladen werden sollen – z. B. next-i18next, next-intl, lingui, next-translate, next-international.
Im Gegensatz dazu fügt Paraglide vor dem Build einen zusätzlichen Schritt hinzu, um JSON in flache Symbole wie const en_my_var = () => 'mein wert' umzuwandeln. Theoretisch ermöglicht dies das Tree-shaking ungenutzter Inhalte auf der Seite. Wie wir sehen werden, hat diese Methode dennoch Nachteile.
Schließlich wendet Intlayer eine Build-Optimierung an, sodass useIntlayer('mein-key') direkt durch den entsprechenden Inhalt ersetzt wird.
Methodik
Für diesen Benchmark haben wir die folgenden Bibliotheken verglichen:
Base App(Ohne i18n-Bibliothek)next-intlayer(v8.7.12)next-i18next(v16.0.5)next-intl(v4.9.1)@lingui/core(v5.3.0)next-translate(v3.1.2)next-international(v1.3.1)@inlang/paraglide-js(v2.15.1)@tolgee/react(v7.0.0)@lingo.dev/compiler(v0.4.0)wuchale(v0.22.11)gt-next(v6.16.5)
Ich habe Next.js Version 16.2.4 mit dem App Router verwendet.
Ich habe eine mehrsprachige App mit 10 Seiten und 10 Sprachen erstellt.
Ich habe vier Ladestrategien verglichen:
Tabelle in einem Modal öffnen, um alle Daten übersichtlich anzuzeigen
| Strategie | Ohne Namespaces (global) | Mit Namespaces (scoped) |
|---|---|---|
| Statisches Laden | Statischer: Alles beim Start im Speicher. | Scoped static: Nach Namespace getrennt; alles beim Start geladen. |
| Dynamisches Laden | Dynamic: Laden bei Bedarf pro Sprache. | Scoped dynamic: Granulares Laden pro Namespace und Sprache. |
Strategie-Zusammenfassung
- Statischer (Static): Einfach; keine Netzwerklatenz nach dem ersten Laden. Nachteil: große Bundle-Größe.
- Dynamic: Reduziert das Anfangsgewicht (Lazy-Loading). Ideal bei vielen Sprachen.
- Scoped static: Hält den Code organisiert (logische Trennung) ohne komplexe zusätzliche Netzwerkanfragen.
- Scoped dynamic: Bester Ansatz für Code-Splitting und Performance. Minimiert den Speicherbedarf, indem nur das geladen wird, was der aktuelle View und die aktive Sprache benötigen.
Was ich gemessen habe:
Ich habe dieselbe mehrsprachige Anwendung in einem echten Browser für jedes Framework ausgeführt und dokumentiert, was tatsächlich über das Netzwerk übertragen wurde und wie lange die Vorgänge dauerten. Die Größenangaben beziehen sich auf den Zustand nach normaler Web-Komprimierung, da dies näher an dem liegt, was Benutzer tatsächlich herunterladen.
Größe der Internationalisierungs-Bibliothek: Nach dem Bundling, Tree-shaking und Minifizieren entspricht die Größe der i18n-Bibliothek der Größe der Provider (z. B.
NextIntlClientProvider) + dem Code der Hooks (z. B.useTranslations) in einer leeren Komponente – ohne das Laden von Übersetzungsdateien. Dies beantwortet die Frage, wie "teuer" die Bibliothek ist, bevor Ihre Inhalte hinzukommen.JavaScript pro Seite: Für jede Benchmark-Route die Menge an Skripten, die der Browser für diesen Besuch abruft, gemittelt über alle Seiten der Suite (und über die Sprachen hinweg). Schwere Seiten sind langsame Seiten.
Leakage von anderen Sprachen: Dies ist der Inhalt derselben Seite, aber in einer anderen Sprache, der fälschlicherweise in die untersuchte Seite geladen wird. Dieser Inhalt ist unnötig und sollte vermieden werden (z. B. Inhalt der
/fr/about-Seite im Bundle der/en/about-Seite).Leakage von anderen Routen: Das gleiche Konzept für andere Ansichten in der App: ob deren Texte mitgeliefert werden, obwohl Sie nur eine einzige Seite geöffnet haben (z. B. Inhalt der
/en/about-Seite im Bundle der/en/contact-Seite). Ein hoher Wert deutet auf schwaches Splitting oder zu weit gefasste Bundles hin.Durchschnittliche Bundle-Größe pro Komponente: Gängige UI-Elemente werden einzeln gemessen, anstatt in einer riesigen App-Zahl unterzugehen. Es zeigt, ob die Internationalisierung alltägliche Komponenten heimlich aufbläht. Wenn Ihre Komponente beispielsweise neu gerendert wird, lädt sie all diese Daten aus dem Speicher. Das Anhängen eines riesigen JSONs an eine Komponente ist wie der Anschluss eines großen Speichers für ungenutzte Daten, der die Performance Ihrer Komponenten verlangsamt.
Reaktionszeit beim Sprachwechsel: Ich wechsle die Sprache über das eigene Steuerelement der App und messe die Zeit, bis die Seite sichtlich umgeschaltet hat – das, was ein Besucher bemerken würde, kein technischer Mikroschritt.
Render-Arbeit nach einem Sprachwechsel: Eine detailliertere Untersuchung: Wie viel Aufwand die Oberfläche betreiben musste, um nach dem Umschalten die neue Sprache zu zeichnen. Nützlich, wenn die "gefühlte" Zeit und die Framework-Kosten auseinanderklaffen.
Initiale Seitenladezeit: Von der Navigation bis zu dem Zeitpunkt, an dem der Browser die Seite für die von mir getesteten Szenarien als vollständig geladen betrachtet. Gut zum Vergleich von Kaltstarts (Cold Starts).
Hydrierungszeit: Sofern die App dies ausgibt: Wie lange der Client benötigt, um Server-HTML in etwas Interaktives zu verwandeln. Ein Bindestrich in den Tabellen bedeutet, dass diese Implementierung in diesem Benchmark keinen zuverlässigen Hydrierungswert geliefert hat.
GitHub-Sterne
GitHub-Sterne sind ein starker Indikator für die Popularität eines Projekts, das Vertrauen der Community und die langfristige Relevanz. Sie sind zwar kein direktes Maß für die technische Qualität, spiegeln jedoch wider, wie viele Entwickler das Projekt nützlich finden, seinen Fortschritt verfolgen und es wahrscheinlich übernehmen werden. Um den Wert eines Projekts einzuschätzen, helfen Sterne dabei, die Traktion verschiedener Alternativen zu vergleichen und Einblicke in das Wachstum des Ökosystems zu gewinnen.
Ergebnisse im Detail
1 - Zu vermeidende Lösungen
Einige Lösungen wie gt-next oder lingo.dev sollten klar vermieden werden. Sie kombinieren Vendor-Lock-in mit einer Verunreinigung Ihrer Codebasis. Trotz vieler Stunden Arbeit ist es mir nicht gelungen, sie zum Laufen zu bringen – weder auf TanStack Start noch auf Next.js.
Aufgetretene Probleme:
(General Translation) ([email protected]):
- Bei einer 110-KB-App fügt
gt-nextmehr als 440 KB hinzu. Quota Exceeded, please upgrade your planbereits beim allerersten Build mit General Translation.- Übersetzungen werden nicht gerendert; ich erhalte den Fehler
Error: <T> used on the client-side outside of <GTProvider>, was anscheinend ein Bug in der Bibliothek ist. - Bei der Implementierung von gt-next stieß ich ebenfalls auf ein Problem mit der Bibliothek:
does not provide an export named 'printAST' - @formatjs/icu-messageformat-parser, was zum Absturz der Anwendung führte. Nach Meldung dieses Problems behob der Maintainer es innerhalb von 24 Stunden. - Die Bibliothek blockiert das statische Rendering von Next.js-Seiten.
(Lingo.dev) (@lingo.dev/[email protected]):
- AI-Quota überschritten, was den Build komplett blockiert – man kann also nicht ohne Bezahlung produktiv gehen.
- Der Compiler überging fast 40 % der übersetzten Inhalte. Ich musste alle
.map-Aufrufe in flache Komponentenblöcke umschreiben, damit es funktioniert. - Ihr CLI ist fehleranfällig und setzte die Konfigurationsdatei grundlos zurück.
- Beim Build löschte es die generierten JSONs vollständig, sobald neuer Inhalt hinzugefügt wurde. Dies konnte dazu führen, dass eine Handvoll Schlüssel mehr als 300 bestehende Schlüssel vernichtete.
2 - Experimentelle Lösungen
(Wuchale) ([email protected]):
Die Idee hinter Wuchale ist interessant, aber noch nicht reif für den Einsatz. Ich stieß auf Reaktivitätsprobleme und musste den Provider-Re-render erzwingen, um die App zum Laufen zu bringen. Die Dokumentation ist zudem recht unklar, was den Einstieg erschwert.
(Paraglide) (@inlang/[email protected]):
Paraglide bietet einen innovativen, gut durchdachten Ansatz. Dennoch funktionierte in diesem Benchmark das beworbene Tree-shaking für meine Next.js- oder TanStack Start-Setups nicht. Der Workflow und die DX sind komplexer als bei anderen Optionen.
Persönlich missfällt mir die Notwendigkeit, JS-Dateien vor jedem Push neu zu generieren, was ein ständiges Risiko für Merge-Konflikte in PRs darstellt. Das Tool scheint zudem stärker auf Vite als auf Next.js ausgerichtet zu sein.
Schließlich nutzt Paraglide im Vergleich zu anderen Lösungen keinen Store (z. B. React Context), um die aktuelle Sprache für das Rendering abzurufen. Für jeden geparsten Node wird die Sprache aus dem localStorage / Cookie etc. angefragt. Dies führt zur Ausführung unnötiger Logik, die die Reaktivität der Komponenten beeinträchtigt.
Hinweis zu Paraglide: Die Lösung injiziert Code in Ihre Codebasis für den Import, wodurch die Metrik 'Bibliotheksgröße' im Benchmark-Bericht fast 0 ist. Codegenerierung ist eine gute Sache, da die verwendete Funktion nur die notwendige Logik enthält (Präfix-Gesamtheit vs. kein Präfix, Cookie vs. Speicher usw.). Im Vergleich dazu führt Intlayer diese Filterung durch Injektionen von Umgebungsvariablen in den Build durch, um den Bundler zu zwingen, Inhalte je nach Logik per Tree-shaking zu entfernen. Dank dessen sind Paraglide und Intlayer am Ende 6- bis 10-mal leichtere Lösungen als i18next oder next-intl.
3 - Akzeptable Lösungen
(Tolgee) (@tolgee/[email protected]):
Tolgee adressiert viele der oben genannten Probleme. Ich fand es schwieriger zu adoptieren als ähnliche Tools. Es bietet keine Typsicherheit, was es zudem erschwert, fehlende Schlüssel zur Kompilierzeit zu finden. Ich musste die Tolgee-Funktionen mit eigenen Funktionen umhüllen, um eine Erkennung fehlender Schlüssel hinzuzufügen.
(Next Intl) ([email protected]):
next-intl ist die derzeit angesagteste Option und diejenige, die KI-Assistenten am häufigsten empfehlen, meiner Ansicht nach jedoch fälschlicherweise. Der Einstieg ist einfach. In der Praxis ist die Optimierung zur Begrenzung von Leakage komplex. Die Kombination aus dynamischem Laden + Namespacing + TypeScript-Typen verlangsamt die Entwicklung enorm. Das Paket ist zudem recht schwer (~13 KB für NextIntlClientProvider + useTranslations, mehr als doppelt so viel wie next-intlayer). next-intl blockierte früher das statische Rendering von Next.js-Seiten. Es bietet einen Helper namens setRequestLocale(). Dies scheint für zentralisierte Dateien wie en.json / fr.json teilweise gelöst zu sein, aber das statische Rendering bricht immer noch ab, wenn Inhalte in Namespaces wie en/shared.json / fr/shared.json / es/shared.json aufgeteilt sind.
(Next I18next) ([email protected]):
next-i18next ist wahrscheinlich die beliebteste Option, da es eine der ersten i18n-Lösungen für JavaScript-Apps war. Es gibt viele Community-Plugins. Es teilt die gleichen großen Nachteile wie next-intl. Das Paket ist besonders schwer (~18 KB für I18nProvider + useTranslation, etwa dreimal so viel wie next-intlayer).
Die Nachrichtenformate unterscheiden sich ebenfalls: next-intl verwendet ICU MessageFormat, während i18next sein eigenes Format nutzt.
(Next International) ([email protected]):
next-international geht die oben genannten Probleme ebenfalls an, unterscheidet sich aber nicht wesentlich von next-intl oder next-i18next. Es enthält scopedT() für Namespace-spezifische Übersetzungen, aber dessen Verwendung hat praktisch keinen Einfluss auf die Bundle-Größe.
(Lingui) (@lingui/[email protected]):
Lingui wird oft gelobt. Persönlich fand ich den lingui extract / lingui compile Workflow komplexer als Alternativen, ohne einen klaren Vorteil. Mir fielen zudem inkonsistente Syntaxen auf, die KIs verwirren (z. B. t(), t'', i18n.t(), <Trans>).
4 - Empfehlungen
(Next Translate) ([email protected]):
next-translate ist meine Hauptempfehlung, wenn Sie eine API im t()-Stil bevorzugen. Es ist durch das next-translate-plugin elegant gelöst und lädt Namespaces über getStaticProps mit einem Webpack / Turbopack Loader. Es ist zudem die leichteste Option hier (~2,5 KB). Für das Namespacing ist die Definition pro Seite oder Route in der Konfiguration gut durchdacht und einfacher zu warten als die Hauptalternativen wie next-intl oder next-i18next. In Version 3.1.2 stellte ich fest, dass das statische Rendering nicht funktionierte; Next.js fiel auf dynamisches Rendering zurück.
(Intlayer) ([email protected]):
Ich werde next-intlayer der Objektivität halber nicht persönlich bewerten, da es meine eigene Lösung ist.
Persönliche Anmerkung
Diese Anmerkung ist persönlicher Natur und beeinflusst die Benchmark-Ergebnisse nicht. In der i18n-Welt sieht man oft einen Konsens um das Muster const t = useTranslation('xx') + <>{t('xx.xx')}</>.
In React-Apps ist das Injizieren einer Funktion als ReactNode meiner Meinung nach ein Anti-Pattern. Es fügt zudem vermeidbare Komplexität und JavaScript-Ausführungs-Overhead hinzu (wenn auch kaum spürbar).