Next.js 16 i18n mit next-intl: Internationalisierung im App Router (2026)
Ein praktischer Leitfaden für Next.js 16 mit next-intl v4: [locale]-Ordner, proxy.ts, Server-Components-Übersetzungen, hreflang, Static Rendering und der Vergleich zu next-i18next, mit lauffähigem TypeScript-Code.
Next.js 16 i18n mit next-intl funktioniert über einen [locale]-Segmentordner im App Router, eine proxy.ts-Datei für die Locale-Erkennung und eine zentrale i18n/request.ts, die pro Request die passenden Übersetzungen lädt. Damit sind Server Components, Static Rendering und hreflang-Tags von Anfang an sauber verdrahtet. Ich schreibe Next.js seit den Pages-Router-Tagen und hab die eingebaute i18n-Konfiguration lange schmerzlich vermisst. Nach mehreren Migrationen von next-i18next auf next-intl v4 muss ich ehrlich sagen: das neue Modell ist besser, aber nur wenn man ein paar App-Router-Fallen kennt.
Der App Router hat die eingebaute i18n-Konfiguration aus next.config.ts ersatzlos entfernt; alles läuft jetzt über einen [locale]-Dynamic-Segmentordner.
next-intl v4 ist die für Server Components empfohlene Bibliothek und arbeitet nativ mit Next.js 16.2 und React 19.2 zusammen.
Statisches Rendering funktioniert nur, wenn du in jedem Layout und in jeder Seite setRequestLocale(locale) aufrufst, sonst fällt alles auf Dynamic Rendering zurück.
Die frühere middleware.ts heißt in Next.js 16 proxy.ts; next-intls Routing-Middleware wird identisch exportiert.
SEO-Tags (hreflang, kanonische URLs) gehören in generateMetadata, nicht in ein globales Layout, sonst verlieren alternative Sprachversionen ihre Zuordnung.
Warum next-intl statt eingebauter i18n?
Kurz gesagt: weil es in Next.js 16 keine eingebaute i18n mehr gibt. Die aus dem Pages Router bekannten i18n.locales- und i18n.defaultLocale-Optionen in next.config.ts haben mit dem App Router nie funktioniert und wurden inzwischen offiziell zurückgezogen. Das Team von Vercel verweist im aktuellen Internationalization-Guide ausdrücklich auf Community-Bibliotheken: next-intl, next-translate, lingui, paraglide-next und andere.
Für Server-Components-lastige Apps ist next-intl in v4 die stimmigste Wahl. Die Bibliothek wurde für den App Router von Grund auf gebaut, unterstützt async-Übersetzungen in RSC, hat einen Router-Wrapper für lokalisierte Pfade und liefert eine typsichere API auf Basis der ICU-Message-Syntax. In meinen produktiven Projekten mit acht bis zwölf Sprachen macht das den entscheidenden Unterschied: die Server-Bundle-Größe bleibt schlank, weil Nachrichten nur pro Request geladen werden, und der Client bekommt nur die für interaktive Komponenten wirklich nötige Teilmenge.
Der zweite Grund ist Wartbarkeit. Ich hab genug alte Projekte gesehen, die zwischen react-intl-Providern, next-i18next-Namespaces und selbstgeschriebenen Fetch-Wrappern eskaliert sind. next-intl ersetzt das durch ein einziges Konfigurationsobjekt, eine getTranslations()-Funktion auf dem Server und einen useTranslations()-Hook auf dem Client, mit identischer API.
Projektstruktur mit [locale]-Segment
Das mentale Umdenken vom Pages Router ist einfach: alles unter app/ zieht in einen app/[locale]/-Ordner. Die Locale ist damit ein normales Dynamic Segment, das du in Layouts und Seiten über params bekommst. Weil params in Next.js 16 asynchron sind, musst du es awaiten.
Der große Vorteil dieses Layouts: Jede URL trägt die Locale, jedes Layout kann sich in der eigenen Sprache rendern, und Static Generation über generateStaticParams multipliziert einfach die Anzahl der Seiten mit der Anzahl der Sprachen. In meinem letzten Projekt haben wir so 1.400 Seiten in vier Sprachen komplett vorgerendert, ohne einen einzigen dynamischen Request auf dem Origin. Für Details zur Rendering-Strategie lohnt ein Blick in unseren Artikel zu Partial Prerendering im App Router.
next-intl v4 installieren und konfigurieren
Ich zeige die Konfiguration mit Next.js 16.2 und next-intl 4.x. Erst die Pakete installieren:
npm install next-intl@^4
Dann das Plugin in next.config.ts einhängen. Das Plugin akzeptiert einen Pfad zur Request-Konfiguration; ich lasse ihn Standard, damit alles unter ./i18n/request.ts liegt.
// next.config.ts
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./i18n/request.ts');
const nextConfig: NextConfig = {
experimental: {
// typedRoutes: true, // optional, aber empfohlen für getypte href
},
};
export default withNextIntl(nextConfig);
Das Routing wird zentral in i18n/routing.ts definiert. Hier landen unterstützte Locales, die Default-Locale und ob die Default-Sprache einen URL-Prefix bekommt (always) oder ohne Prefix bedient wird (as-needed). Ich empfehle für neue Projekte always: konsistente URLs, klare Cache-Keys, und Google zeigt sofort das richtige hreflang-Ziel.
Die Request-Konfiguration lädt für jeden Server-Request die passende Nachrichtendatei. In Produktion wird die Datei dank ES-Module-Caching pro Locale nur einmal geladen, du zahlst also keinen Preis pro Request:
Und die typsicheren Navigations-Wrapper: diese Link- und redirect-Exporte kennen die konfigurierten Locales und verhindern, dass man versehentlich auf /about statt /de/about verlinkt.
Mit Next.js 16 wurde die Konvention middleware.ts in proxy.ts umbenannt, und next-intls Routing-Handler wird genauso exportiert. Die Middleware liest den Accept-Language-Header, prüft ein optionales NEXT_LOCALE-Cookie und leitet auf die passende URL um. Der matcher sorgt dafür, dass statische Assets, API-Routen und _next/*-Pfade unberührt bleiben, sonst würde jedes Bild einen Redirect auslösen.
Ein Detail, das mich beim ersten Projekt eine halbe Stunde gekostet hat: wenn du Auth mit einer eigenen Middleware kombinierst, brauchst du eine komponierte Middleware, sonst wird nur eine der beiden ausgeführt. Für Auth.js v5 zeige ich das Zusammenspiel in unserer Anleitung zu Auth.js v5 mit Next.js 16. Grundsätzlich rufst du in proxy.ts zuerst den next-intl-Handler auf und leitest das Ergebnis in die Auth-Middleware weiter, oder umgekehrt, je nachdem welche Reihenfolge dein Redirect-Verhalten erwartet.
Server Components vs. Client Components
Der zentrale Vorteil von next-intl im App Router: Übersetzungen funktionieren in beiden Welten mit fast identischer API. In Server Components nutzt du getTranslations(), das ein async-Aufruf ist und automatisch die Request-Konfiguration verwendet.
// app/[locale]/page.tsx
import { getTranslations } from 'next-intl/server';
export default async function HomePage() {
const t = await getTranslations('home');
return (
<main>
<h1>{t('title')}</h1>
<p>{t('subtitle')}</p>
</main>
);
}
Für Client Components musst du im Root-Layout einen Provider setzen und dann den useTranslations()-Hook verwenden. Wichtig: der Provider bekommt die Nachrichten server-seitig übergeben, der Client lädt sie also nicht separat nach.
// components/CartButton.tsx
'use client';
import { useTranslations } from 'next-intl';
export function CartButton({ itemCount }: { itemCount: number }) {
const t = useTranslations('cart');
return (
<button>
{t('items', { count: itemCount })}
</button>
);
}
Der Nachrichten-Eintrag cart.items nutzt hier die ICU-Plural-Syntax:
{
"cart": {
"items": "{count, plural, =0 {Warenkorb leer} one {# Artikel} other {# Artikel}}"
}
}
Statisches Rendering mit setRequestLocale
Wer wie ich aus dem Pages Router kommt, denkt bei i18n zuerst an getStaticPaths. Im App Router heißt das Pendant generateStaticParams, und der einzige zusätzliche Handgriff für next-intl ist ein Aufruf von setRequestLocale(locale) ganz oben in jedem Layout und jeder Page, die statisch gerendert werden soll. Ohne diesen Aufruf fällt die Seite auf Dynamic Rendering zurück, weil next-intl die Locale sonst nur aus dem Request-Header ableiten kann (und der ist bei Build-Time nicht vorhanden).
Ein häufiger Fehler: der Aufruf wird im Root-Layout gemacht, aber in verschachtelten Layouts vergessen. Der Effekt ist heimtückisch, weil die Seite lokal noch funktioniert. Erst im next build-Log siehst du in der Route-Tabelle ein ƒ (Dynamic) statt ○ (Static), und die Time-to-First-Byte steigt in Produktion um zweistellige Millisekunden. Deshalb: jede Layout- und Page-Datei unter [locale] ruft setRequestLocale auf.
Sprachumschalter und lokalisierte Links
Für internen Traffic benutzt du ausschließlich die Wrapper aus i18n/navigation.ts. Sie erweitern die App-Router-Link-Komponente um automatisches Locale-Prefixing. Ein Sprachumschalter, der auf demselben Pfad bleibt und nur die Locale tauscht, sieht so aus:
Der Trick mit useTransition: der Wechsel läuft als non-blocking Transition, sodass die alte Seite sichtbar bleibt, bis das neue Locale gestreamt ist. Ohne startTransition siehst du bei langsameren Verbindungen einen kurzen weißen Blitz. Für dynamische Datenabhängigkeiten und Streaming ist unser Deep-Dive zu Streaming SSR und Suspense ergänzend hilfreich.
SEO: hreflang und generateMetadata
Ohne korrekte hreflang-Angaben zeigt Google im schlimmsten Fall die englische URL an einen deutschen Nutzer aus, oder umgekehrt. Die saubere Lösung: in generateMetadata pro Route die Alternates zu setzen. next-intl hilft mit getPathname, den lokalisierten Pfad für jede Sprache zu berechnen.
Das erzeugt im HTML <link rel="alternate" hreflang="de" href="/de/about" /> für jede Sprache plus einen x-default. Wichtig: Google verlangt eine gegenseitige Verlinkung. Jede Sprachversion muss alle anderen aufführen, sonst werden die Signale ignoriert. Vergiss außerdem nicht die Sitemap: eine sprachbewusste sitemap.ts gehört ins Root-Verzeichnis, nicht unter [locale]. Mehr zur Metadaten-API im App Router steht in unserem Artikel zu dynamischer SEO mit generateMetadata. Wer die offizielle Empfehlung nachlesen will, findet die Details im Google Search Central hreflang-Guide.
next-intl vs. next-i18next: Was passt wann?
Beide Bibliotheken sind 2026 aktiv. next-i18next hat mit v16 endlich App-Router-Support bekommen und ist attraktiv, wenn du bereits ein i18next-Ökosystem mit CI-basiertem Übersetzungs-Workflow, Namespaces und LingoHub- oder Locize-Anbindung hast. next-intl ist konzeptionell schlanker und für Server Components von Anfang an gebaut.
Kriterium
next-intl v4
next-i18next v16
App-Router-Support
Nativ, seit v3
Ab v16 (2026 nachgezogen)
Server-Components-API
getTranslations() async
Wrapper um i18next-Instanz
Message-Format
ICU MessageFormat
i18next-Syntax (Plurals, Interpolation)
Bundle-Größe (Client)
~15 KB gzipped
~45 KB gzipped (mit i18next)
TypeScript-Autocomplete
Eingebaut über global.d.ts
Über Plugin manuell konfigurierbar
Ökosystem (TMS, CLI-Tools)
Wachsend, Cloud-Anbindung nötig
Sehr groß, i18next-Ökosystem seit 2016
Static Rendering
Über setRequestLocale
Über eigene Instance pro Request
Empfehlung 2026
Neue App-Router-Projekte
Migration bestehender i18next-Setups
In der Praxis: wer bei Null anfängt und den App Router konsequent nutzt, sollte next-intl wählen. Wer aus einem next-i18next-Projekt migriert und ein volles i18next-basiertes Übersetzungssystem betreibt, kann bei v16 bleiben, sollte aber proxy.ts-Support und den neuen Server-Wrapper explizit einbauen. Für die Migration vom Pages Router lohnt außerdem ein Blick in unseren Beitrag zu Turbopack in der Produktion, weil beide Umstellungen meist zusammen fallen.
Typsichere Übersetzungsschlüssel
Ein Grund, warum ich next-intl in Teams liebe: die Nachrichten-Keys werden vollständig typisiert, sobald du in global.d.ts die Messages-Struktur exportierst. Danach schreit TypeScript, sobald jemand t('titel') statt t('title') tippt, und das im Editor, nicht in Produktion.
Für große Teams empfehle ich zusätzlich ein Lint-Skript, das ungenutzte Keys aus messages/*.json flaggt, und einen CI-Check, der die JSON-Dateien gegen die Default-Locale abgleicht. Die offizielle next-intl-Dokumentation liefert für beides fertige Rezepte.
Häufige Fragen
Wie fügt man i18n zu einem bestehenden Next.js-16-App-Router-Projekt hinzu?
Verschiebe alle Dateien aus app/ in einen neuen app/[locale]/-Ordner, installiere next-intl, richte i18n/routing.ts, i18n/request.ts und proxy.ts ein und wickle das Root-Layout in einen NextIntlClientProvider. Für statisches Rendering ruf in jedem Layout und in jeder Page setRequestLocale(locale) auf.
Was ist der Unterschied zwischen next-intl und next-i18next?
next-intl ist eine für den App Router gebaute Bibliothek mit nativer Server-Components-API und ICU-Message-Format. next-i18next ist ein Wrapper um das i18next-Ökosystem, hat App-Router-Support erst seit v16 und ist attraktiv, wenn du bereits i18next-Übersetzungs-Tools nutzt.
Warum werden meine Seiten dynamisch statt statisch gerendert?
Der häufigste Grund ist ein fehlender setRequestLocale(locale)-Aufruf. Ohne diesen kann next-intl die Locale nicht zur Build-Zeit bestimmen, und Next.js muss auf Dynamic Rendering ausweichen. Prüfe die Route-Tabelle im next build-Log auf ƒ- statt ○-Markierungen.
Wie handhabt man SEO in einer mehrsprachigen Next.js-App?
Setze in generateMetadata pro Route die alternates.languages mit den lokalisierten URLs sowie einen x-default-Eintrag. Vergiss die kanonische URL nicht und generiere eine sprachbewusste sitemap.ts im Root, die alle Locale-Varianten jeder Seite auflistet.
Kann next-intl mit Server Actions und Formularen umgehen?
Ja. In Server Actions rufst du getTranslations() genauso wie in RSC auf, weil beide im Server-Kontext laufen. Für Fehlermeldungen aus useActionState übergibst du die übersetzten Strings idealerweise als Rückgabewert der Action, nicht als geworfene Exception.
Praktischer Guide zur Next.js Metadata API: statisch und dynamisch mit generateMetadata, Open-Graph-Bilder via ImageResponse, JSON-LD Rich Results, sitemap.ts und robots.ts, plus konkrete Fehlerbehebung aus echten Produktions-Deploys.
Streaming SSR in Next.js 16 mit Suspense-Boundaries, loading.tsx und Turbopack. Fix fuer Request-Waterfalls, Skeleton-Fallbacks und ErrorBoundary im App Router.
So deployst du Next.js 16 als schlankes Docker-Image: Standalone Output aktivieren, ein Multi-Stage-Dockerfile mit Alpine 3.20 und Node 22 bauen, Sharp und Environment-Variablen richtig konfigurieren und in unter drei Tagen produktiv gehen.