Next.js‑i18n mit App Router
Server Components, ISR und edge‑optimierte Übersetzungen für Next.js‑Anwendungen.
middlewaremiddleware.tsgetRequestConfigi18n/request.tsgetMessages()app/[locale]/page.tsxuseTranslations()components/Hero.tsxSetup
Set up in 4 steps
Installation
Füge @better-i18n/next und next-intl zu deinem Projekt hinzu.
npm install @better-i18n/next next-intlMiddleware zur Locale-Erkennung hinzufügen
Die Middleware liest den Accept-Language-Header und das URL-Präfix, um die Locale des Nutzers zu erkennen und entsprechend weiterzuleiten.
import { createBetterI18nMiddleware } from '@better-i18n/next';
export default createBetterI18nMiddleware({
project: 'your-org/your-project',
defaultLocale: 'en',
localePrefix: 'always',
});
export const config = { matcher: ['/((?!api|_next).*)'] };Nachrichten in einer Server Component laden
Verwende getMessages() in deinem Root-Layout, um Übersetzungen serverseitig zu laden und an BetterI18nProvider zu übergeben.
// app/[locale]/layout.tsx
import { BetterI18nProvider } from '@better-i18n/next/client';
import { getMessages } from '@better-i18n/next/server';
const config = { project: 'your-org/your-project', defaultLocale: 'en' };
export default async function RootLayout({ children, params }) {
const { locale } = await params;
const messages = await getMessages(config, locale);
return (
<html lang={locale}>
<body>
<BetterI18nProvider locale={locale} messages={messages} config={config}>
{children}
</BetterI18nProvider>
</body>
</html>
);
}Übersetzungen in Client Components verwenden
Rufe useTranslations() in jeder beliebigen Client Component auf. Die Nachrichten sind bereits vom Server hydratisiert – kein zusätzlicher Fetch nötig.
'use client';
import { useTranslations } from 'next-intl';
export function HeroSection() {
const t = useTranslations('home');
return <h1>{t('title')}</h1>;
}Routing
Edge-Laufzeitumgebung und Erkennung der Ländereinstellung
Führen Sie die Erkennung der Ländereinstellungen und das Laden von Nachrichten am Rand durch, um weltweit Reaktionszeiten von unter 50 ms zu erzielen.
Middleware-Einrichtung
Füge deiner Next.js-App mit einer einzigen Middleware-Datei Locale-Erkennung und Routing hinzu.
// middleware.ts — locale detection
import { createBetterI18nMiddleware } from '@better-i18n/next'
export default createBetterI18nMiddleware({
project: 'your-org/your-project',
defaultLocale: 'en',
localePrefix: 'always',
})
export const config = { matcher: ['/((?!api|_next).*)'] }// middleware.ts — Edge-based locale detection
import { NextRequest, NextResponse } from 'next/server';
const SUPPORTED_LOCALES = ['en', 'de', 'fr', 'ja', 'es'] as const;
const DEFAULT_LOCALE = 'en';
function getPreferredLocale(request: NextRequest): string {
// 1. Check URL prefix
const pathname = request.nextUrl.pathname;
const urlLocale = SUPPORTED_LOCALES.find(
(l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
);
if (urlLocale) return urlLocale;
// 2. Check cookie
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
if (cookieLocale && SUPPORTED_LOCALES.includes(cookieLocale as any)) {
return cookieLocale;
}
// 3. Parse Accept-Language header
const acceptLang = request.headers.get('accept-language') ?? '';
const preferred = acceptLang
.split(',')
.map((part) => part.split(';')[0].trim().substring(0, 2))
.find((code) => SUPPORTED_LOCALES.includes(code as any));
return preferred ?? DEFAULT_LOCALE;
}
export function middleware(request: NextRequest) {
const locale = getPreferredLocale(request);
const { pathname } = request.nextUrl;
const hasLocale = SUPPORTED_LOCALES.some(
(l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`
);
if (!hasLocale) {
return NextResponse.redirect(
new URL(`/${locale}${pathname}`, request.url)
);
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
};Edge-kompatibles Laden von Nachrichten
Cache Übersetzungen am Edge mit einem leichtgewichtigen In-Memory-TTL-Cache für sofortige Antworten.
// lib/edge-messages.ts — Edge-compatible message loading
const messageCache = new Map<string, { data: Record<string, string>; ts: number }>();
const TTL = 60_000; // 1 minute cache at edge
export async function getEdgeMessages(
locale: string,
namespace: string
): Promise<Record<string, string>> {
const cacheKey = `${locale}:${namespace}`;
const cached = messageCache.get(cacheKey);
if (cached && Date.now() - cached.ts < TTL) {
return cached.data;
}
const response = await fetch(
`https://cdn.better-i18n.com/your-org/your-project/${locale}/${namespace}.json`,
{ next: { revalidate: 60 } }
);
const data = await response.json();
messageCache.set(cacheKey, { data, ts: Date.now() });
return data;
}Edge-API-Route mit i18n
Gib übersetzte API-Antworten aus Edge-Funktionen mit minimalem Cold Start zurück.
// app/api/translate/route.ts — Edge API route with i18n
import { getEdgeMessages } from '@/lib/edge-messages';
export const runtime = 'edge';
export async function GET(request: Request) {
const url = new URL(request.url);
const locale = url.searchParams.get('locale') ?? 'en';
const key = url.searchParams.get('key') ?? '';
const messages = await getEdgeMessages(locale, 'api-responses');
const translated = messages[key] ?? key;
return Response.json({ text: translated, locale });
}Rendering
ISR und Internationalisierung
Kombinieren Sie inkrementelle statische Regenerierung mit i18n für schnelle, stets aktuelle mehrsprachige Seiten.
revalidate = 3600app/[locale]/layout.tsx~60 minrevalidate = 1800app/[locale]/[slug]/page.tsx~30 minrevalidatePath()app/api/revalidate/route.ts< 1 minSchnellstart
Fügen Sie i18n zu Ihrer Next.js‑App mit nur wenigen Zeilen Code hinzu.
// app/[locale]/page.tsx
import { getTranslations } from 'next-intl/server';
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'home' });
return (
<main>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</main>
);
}// app/[locale]/layout.tsx — ISR with i18n
import { getMessages } from '@better-i18n/next/server';
import { BetterI18nProvider } from '@better-i18n/next/client';
export const revalidate = 3600; // Revalidate every hour
const config = { project: 'your-org/your-project', defaultLocale: 'en' };
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const messages = await getMessages(config, locale);
return (
<BetterI18nProvider locale={locale} messages={messages} config={config}>
{children}
</BetterI18nProvider>
);
}ISR mit generateStaticParams
Rendere Seiten für jede Locale zur Build-Zeit vor und aktualisiere sie anschließend planmäßig mit ISR.
// app/[locale]/[slug]/page.tsx — Generate static pages per locale
import { getMessages } from '@better-i18n/next/server';
const config = { project: 'your-org/your-project', defaultLocale: 'en' };
export async function generateStaticParams() {
const locales = ['en', 'de', 'fr', 'ja'];
const slugs = await fetchAllSlugs();
return locales.flatMap((locale) =>
slugs.map((slug) => ({ locale, slug }))
);
}
export const revalidate = 1800; // ISR: refresh every 30 min
export default async function Page({
params,
}: {
params: Promise<{ locale: string; slug: string }>;
}) {
const { locale, slug } = await params;
const messages = await getMessages(config, locale, { namespaces: ['blog'] });
return <article><h1>{messages.blog[slug + '.title']}</h1></article>;
}On-Demand-Revalidierung
Löse eine ISR-Revalidierung aus, wenn Übersetzungen aktualisiert werden – über den Publish-Webhook von Better I18N.
// app/api/i18n/revalidate/route.ts — On-demand ISR for translation updates
import { createRevalidateHandler } from '@better-i18n/next/revalidate';
// Called by the Better i18n publish webhook — verifies the HMAC signature,
// then revalidates the paths/tags below.
export const POST = createRevalidateHandler({
secret: process.env.BETTER_I18N_WEBHOOK_SECRET!,
revalidatePaths: ['/'],
revalidateTags: ['i18n-messages'],
});Advanced
Fortgeschrittene Muster
Verschachtelte Layouts, parallele Routen und Serveraktionen mit typsicheren Übersetzungen.
// app/[locale]/dashboard/layout.tsx — Nested layout with namespace
import { getMessages } from '@better-i18n/next/server';
import { BetterI18nProvider } from '@better-i18n/next/client';
import { DashboardNav } from '@/components/DashboardNav';
const config = { project: 'your-org/your-project', defaultLocale: 'en' };
export default async function DashboardLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// Load the dashboard-specific namespace alongside common messages
const messages = await getMessages(config, locale, {
namespaces: ['common', 'dashboard'],
});
return (
<BetterI18nProvider locale={locale} messages={messages} config={config}>
<DashboardNav />
<main>{children}</main>
</BetterI18nProvider>
);
}Parallele Routen mit i18n
Lade Übersetzungen unabhängig in parallelen Routen-Slots für modulare, locale-bewusste Layouts.
// app/[locale]/@analytics/page.tsx — Parallel route with i18n
import { getTranslations } from 'next-intl/server';
export default async function AnalyticsSlot({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'analytics' });
return (
<section aria-label={t('title')}>
<h2>{t('title')}</h2>
<p>{t('description')}</p>
</section>
);
}
// app/[locale]/layout.tsx — Consuming parallel routes
export default function Layout({
children,
analytics,
notifications,
}: {
children: React.ReactNode;
analytics: React.ReactNode;
notifications: React.ReactNode;
}) {
return (
<div>
<main>{children}</main>
<aside>{analytics}</aside>
<aside>{notifications}</aside>
</div>
);
}Server Actions mit Übersetzung
Gib übersetzte Validierungsfehler und Erfolgsmeldungen aus Server Actions zurück.
// app/[locale]/contact/actions.ts — Server action with i18n
'use server';
import { getTranslations } from 'next-intl/server';
import { headers } from 'next/headers';
export async function submitContactForm(formData: FormData) {
const headersList = await headers();
// Set by createBetterI18nMiddleware — see the routing section above
const locale = headersList.get('x-locale') ?? 'en';
const t = await getTranslations({ locale, namespace: 'contact' });
const email = formData.get('email') as string;
const message = formData.get('message') as string;
if (!email || !message) {
return { error: t('validation.required') };
}
try {
await sendEmail({ email, message, locale });
return { success: t('form.success') };
} catch {
return { error: t('form.error') };
}
}
// app/[locale]/contact/page.tsx — Using the server action
'use client';
import { useTranslations } from 'next-intl';
import { submitContactForm } from './actions';
export default function ContactPage() {
const t = useTranslations('contact');
return (
<form action={submitContactForm}>
<label>{t('form.email')}</label>
<input name="email" type="email" required />
<label>{t('form.message')}</label>
<textarea name="message" required />
<button type="submit">{t('form.submit')}</button>
</form>
);
}Fehlerbehebung bei häufigen i18n-Problemen
Beheben Sie Probleme mit der Hydration, fehlende Locale-Fallbacks und Unterschiede bei der Datums-/Zahlenformatierung.
// Fix: Hydration mismatch with date/number formatting
// Problem: Server renders "1,000" but client renders "1.000"
// Solution: BetterI18nProvider already passes an explicit timeZone down to
// NextIntlClientProvider, so server and client share the same formatting locale.
// app/[locale]/layout.tsx
import { getFormatter } from 'next-intl/server';
export default async function Layout({ children, params }: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
// Pre-format on server with the explicit locale
const format = await getFormatter({ locale });
return (
<html lang={locale} suppressHydrationWarning>
<body>{children}</body>
</html>
);
}
// components/Price.tsx — Client component
'use client';
import { useFormatter } from 'next-intl';
export function Price({ amount }: { amount: number }) {
const format = useFormatter();
// useFormatter automatically uses the locale/timeZone from BetterI18nProvider
// ensuring server and client render the same output
return <span>{format.number(amount, { style: 'currency', currency: 'USD' })}</span>;
}Locale-Fallback-Kette
Definiere Fallback-Ketten, damit regionale Varianten wie pt-BR auf pt und dann auf en zurückfallen.
// lib/i18n-config.ts — Locale fallback chain
const FALLBACK_CHAIN: Record<string, string[]> = {
'pt-BR': ['pt', 'en'],
'zh-TW': ['zh-CN', 'en'],
'en-GB': ['en'],
'de-AT': ['de', 'en'],
};
export function resolveMessages(
locale: string,
allMessages: Record<string, Record<string, string>>
): Record<string, string> {
const chain = FALLBACK_CHAIN[locale] ?? ['en'];
const primary = allMessages[locale] ?? {};
// Merge fallback messages (primary overrides fallbacks)
return chain.reduceRight(
(merged, fallbackLocale) => ({
...merged,
...(allMessages[fallbackLocale] ?? {}),
}),
primary
);
}Konsistente Datumsformatierung
Vermeide Datumsabweichungen zwischen Server und Client, indem du timeZone explizit auf UTC setzt.
// components/LocalizedDate.tsx — Consistent date formatting
'use client';
import { useFormatter, useLocale } from 'next-intl';
export function LocalizedDate({ date }: { date: Date | string }) {
const format = useFormatter();
const locale = useLocale();
const dateObj = typeof date === 'string' ? new Date(date) : date;
return (
<time dateTime={dateObj.toISOString()}>
{format.dateTime(dateObj, {
year: 'numeric',
month: 'long',
day: 'numeric',
// Explicitly set timeZone to avoid server/client mismatch
timeZone: 'UTC',
})}
</time>
);
}Capabilities
Features
Funktioniert mit beliebten Next.js i18n-Bibliotheken
Better I18N ersetzt nicht Ihre bevorzugte i18n-Bibliothek — es ist die Übersetzungsmanagement-Schicht, die sie noch leistungsfähiger macht.
Better I18N + next-intl
Die beliebteste Next.js i18n-Bibliothek mit App Router-Unterstützung, typsicheren Nachrichten und ICU-Syntax.Better I18N synchronisiert Übersetzungen direkt im next-intl JSON-Format. Verwalten Sie im Dashboard, deployen Sie sofort über CDN.
Better I18N + next-i18next
Bewährte i18n-Bibliothek für Next.js basierend auf i18next. Ideal für Pages Router und Migration zum App Router.Export im i18next-kompatiblen Namespace-JSON. Better I18N übernimmt den Übersetzungsworkflow, next-i18next die Laufzeit.
Better I18N + Lingui
Leichtgewichtige, makro-basierte i18n-Bibliothek mit exzellenter DX und automatischer Nachrichtenextraktion.Extrahieren Sie Nachrichten mit Lingui CLI, verwalten Sie Übersetzungen in Better I18N, synchronisieren Sie automatisch über GitHub-Integration.
Related Articles
en vs en-US: How to Name Locale Files Without Breaking Your i18n Pipeline
You are setting up i18n. You create the folder. Then you stop, because you have to name the first file and there are two obvious answers: locales/en.json...
Read More →Introducing the Better i18n CLI: Manage Translations from Your Terminal
Until now, managing translation keys on Better i18n meant using the dashboard UI or connecting an MCP server to your AI assistant. Both work great — but...
Read More →How to Add i18n to Shopify Hydrogen (Complete Guide)
Shopify Hydrogen is the modern way to build custom storefronts — but adding internationalization (i18n) can be challenging. You need locale-aware routing,...
Read More →Ähnlich
Entdecken Sie weitere Leitfäden zu Frameworks
Mit Next.js‑i18n loslegen
Kostenloser Tarif verfügbar. Keine Kreditkarte erforderlich.