Next.js i18n im großen Maßstab: 30 Sprachen, 91K Seiten, Vercel ISR
Next.js i18n im großen Stil: 30 Sprachen, 91K Seiten, Vercel ISR
Letztes Jahr haben wir ein Next.js-Projekt ausgeliefert, das mich immer noch ein wenig nervös macht, wenn ich daran denke. Dreißig Sprachen. Über 91.000 statisch generierte Seiten. Vercel ISR hält alles aktuell. Das ist die Art von Projekt, bei dem eine falsche architektonische Entscheidung bedeutet, dass man sich 4-Stunden-Builds, 800 $ monatliche Hosting-Rechnungen oder – im schlimmsten Fall – eine Website ansieht, die in Koreanisch einfach nicht funktioniert.
Dies ist die Geschichte, wie wir es richtig gemacht haben (und die Teile, wo wir anfangs nicht richtig lagen). Wenn du eine große internationale Next.js-Anwendung entwickelst und dich fragst, ob ISR das wirklich in der Produktion bewältigen kann, ist dieser Artikel für dich.

Inhaltsverzeichnis
- Das Problem: Warum 91K Seiten ein anderes Tier sind
- Architektonische Entscheidungen, die wir früh getroffen haben
- Next.js i18n für 30 Locales einrichten
- Die ISR-Strategie, die wirklich funktioniert hat
- Content-Pipeline und Headless-CMS-Integration
- Leistungsergebnisse und Core Web Vitals
- Kostenaufschlüsselung bei Vercel
- Fehler, die wir gemacht haben und wie wir sie behoben haben
- Wann du diesen Stack verwenden solltest (und wann nicht)
- Häufig gestellte Fragen
Das Problem: Warum 91K Seiten ein anderes Tier sind
Lass mich die Szene setzen. Der Kunde war eine große E-Commerce-Marke, die sich in 30 Märkte expandierte. Jeder Markt brauchte:
- Lokalisierte Produktseiten (~2.800 Produkte × 30 Locales = 84.000 Seiten)
- Kategorieseiten (~120 Kategorien × 30 Locales = 3.600 Seiten)
- CMS-gesteuerte Marketing-Seiten (~120 × 30 = 3.600 Seiten)
- Gesamt: ungefähr 91.200 eindeutige URLs
Mit einfachem getStaticPaths und vollständiger statischer Generierung würde der anfängliche Build zwischen 3 und 5 Stunden dauern. Das ist kein Tippfehler. Wir haben frühe Prototypen gemessen und zugesehen, wie die Zahl gestiegen ist. Jeder Deploy würde bedeuten, dass es stundenlange Ausfallzeiten gibt, und das Content-Team wollte mehrmals täglich Updates veröffentlichen.
SSR war auch keine Option. Die Traffic-Muster des Kunden zeigten massive Spitzen während Verkaufsveranstaltungen – wir sprechen von 50.000 gleichzeitigen Benutzern. Die Render-Engine mit 91K möglichen Seitenvarianten unter dieser Last würde ernsthafte Rechnerleistung erfordern und Latenz einführen, die die Conversion-Raten killt.
ISR war die Antwort. Aber ISR in diesem Umfang hat seine eigenen Herausforderungen, auf die dich die Next.js-Dokumentation nicht wirklich vorbereitet.
Architektonische Entscheidungen, die wir früh getroffen haben
Bevor wir eine einzige Codezeile i18n-Code schrieben, trafen wir drei architektonische Entscheidungen, die uns später Monate Schmerzen ersparten.
Entscheidung 1: Subpath-Routing statt Domains
Next.js unterstützt zwei i18n-Strategien: Subpath-Routing (/fr/products/...) und Domain-Routing (fr.example.com). Wir wählten Subpath-Routing. Hier ist warum:
| Faktor | Subpath-Routing | Domain-Routing |
|---|---|---|
| DNS/SSL-Komplexität | Single Domain | 30 Domains/Subdomains zu verwalten |
| Vercel-Bereitstellung | Ein Projekt | Ein Projekt (aber Domain-Config-Overhead) |
| SEO-Link-Equity | Konsolidiert auf einer Domain | Auf Domains aufgeteilt |
| CDN-Cache-Effizienz | Besser (gemeinsamer Edge-Cache) | Fragmentiert |
| Analytics-Setup | Einfacher | 30 Properties oder komplexe Filterung |
Für die meisten Projekte mit weniger als 50 Locales ist Subpath-Routing der richtige Weg. Domain-Routing macht Sinn, wenn du länderspezifische TLDs aus rechtlichen Gründen benötigst oder wenn deine Märkte grundlegend unterschiedliche Content-Architekturen haben.
Entscheidung 2: next-intl über next-i18next
Wir evaluierten beide Bibliotheken ausführlich. Im Jahr 2025 hat sich next-intl (v4.x) zur stärkeren Wahl für App-Router-Projekte entwickelt, obwohl wir für diesen Build auf Pages Router waren. Selbst auf Pages Router gab uns next-intl:
- Bessere TypeScript-Unterstützung mit typensicheren Message-Keys
- Kleineres Client-Bundle (etwa 2,1 KB gzip vs ~5 KB für next-i18next)
- Native Unterstützung für ICU-Message-Format (Plurals, Geschlecht, Zahlenformatierung)
- Einfachere Konfiguration für ISR-Seiten
Entscheidung 3: Teilweise statische Generierung + ISR
Dies war die große. Anstatt zu versuchen, alle 91K Seiten zum Build-Zeitpunkt statisch zu generieren, generierten wir nur die Seiten mit dem höchsten Traffic vor (etwa 8.000) und ließen ISR den Rest bei Bedarf handhaben.
// pages/[locale]/products/[slug].tsx
export async function getStaticPaths() {
// Nur die Top 100 Produkte × Top 5 Locales vorab generieren
const topProducts = await getTopProducts(100);
const primaryLocales = ['en', 'de', 'fr', 'es', 'ja'];
const paths = topProducts.flatMap(product =>
primaryLocales.map(locale => ({
params: { slug: product.slug, locale },
}))
);
return {
paths,
fallback: 'blocking', // ISR handhaben den Rest
};
}
Das reduzierte unsere Build-Zeit von über 3 Stunden auf etwa 12 Minuten. Die restlichen 83.000 Seiten werden beim ersten Request generiert und am Edge gecacht.

Next.js i18n für 30 Locales einrichten
Die integrierte i18n-Konfiguration von Next.js in next.config.js handhabet Locale-Erkennung und Routing. Hier ist, wie unsere Konfiguration aussah (abgekürzt):
// next.config.js
const nextConfig = {
i18n: {
locales: [
'en', 'de', 'fr', 'es', 'it', 'pt', 'nl', 'pl', 'cs', 'da',
'sv', 'fi', 'nb', 'hu', 'ro', 'bg', 'hr', 'sk', 'sl', 'el',
'tr', 'ja', 'ko', 'zh-CN', 'zh-TW', 'th', 'vi', 'id', 'ms', 'ar'
],
defaultLocale: 'en',
localeDetection: false, // Wir handhaben dies selbst
},
};
Ein paar Dinge zu beachten. Wir haben localeDetection deaktiviert, da die integrierte Erkennung (basierend auf Accept-Language-Headern) Probleme mit ISR-Caching verursachte. Wenn der Vercel-CDN eine Seite cached, muss das Locale deterministisch aus der URL sein, nicht aus Headern. Das Zulassen von automatischer Umleitung basierend auf der Browser-Sprache bedeutete Cache-Misses und inkonsistentes Verhalten.
Stattdessen bauten wir eine benutzerdefinierte Locale-Erkennungs-Middleware auf, die nur auf dem Root-Pfad läuft:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const SUPPORTED_LOCALES = ['en', 'de', 'fr', /* ... */];
const DEFAULT_LOCALE = 'en';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Nur auf Root-Pfad umleiten
if (pathname === '/') {
const acceptLanguage = request.headers.get('accept-language') || '';
const preferred = acceptLanguage.split(',')[0]?.split('-')[0] || DEFAULT_LOCALE;
const locale = SUPPORTED_LOCALES.includes(preferred) ? preferred : DEFAULT_LOCALE;
return NextResponse.redirect(new URL(`/${locale}`, request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/'],
};
Translations-Dateistruktur
Bei 30 Sprachen wird die Verwaltung von Translations-Dateien zu einem echten Problem. Wir organisierten Translations nach Namespace:
messages/
├── en/
│ ├── common.json
│ ├── product.json
│ ├── checkout.json
│ └── marketing.json
├── de/
│ ├── common.json
│ ├── product.json
│ └── ...
└── ar/
└── ...
Die Gesamt-Translation-Payload über alle Sprachen betrug etwa 4,2 MB. Aber da wir Translations pro-Seite mit getStaticProps laden, lädt jede einzelne Seite nur 15-40 KB Translations-Daten für ihr Locale und ihren Namespace. Das ist entscheidend – du willst nicht alle 30 Locales zum Client schicken.
export async function getStaticProps({ locale }: GetStaticPropsContext) {
return {
props: {
messages: {
...(await import(`../../messages/${locale}/common.json`)).default,
...(await import(`../../messages/${locale}/product.json`)).default,
},
},
revalidate: 300, // ISR: revalidate alle 5 Minuten
};
}
RTL-Unterstützung für Arabisch
Arabisch war die einzige RTL-Sprache in unserem Set. Wir handhabten es mit einem einfachen Layout-Wrapper:
const direction = locale === 'ar' ? 'rtl' : 'ltr';
return (
<html lang={locale} dir={direction}>
<body className={direction === 'rtl' ? 'font-arabic' : 'font-sans'}>
{children}
</body>
</html>
);
Plus Tailwinds rtl:-Variante für Abstände und Layout-Anpassungen. Das funktionierte überraschend gut – vielleicht 5 % unseres CSS brauchten RTL-spezifische Overrides.
Die ISR-Strategie, die wirklich funktioniert hat
ISR (Incremental Static Regeneration) ist der Held dieser Geschichte, aber um es gut im großen Stil zu verwenden, musst du verstehen, wie Vercels Infrastruktur wirklich funktioniert.
Revalidierungs-Timing
Wir verwendeten unterschiedliche Revalidierungs-Perioden je nach Content-Typ:
| Seiten-Typ | Revalidierungs-Periode | Begründung |
|---|---|---|
| Produktseiten | 300s (5 min) | Preise/Bestand ändern sich häufig |
| Kategorieseiten | 900s (15 min) | Produktlisten aktualisieren sich weniger oft |
| Marketing/CMS-Seiten | 3600s (1 Stunde) | Content-Änderungen sind geplant |
| Homepage pro Locale | 600s (10 min) | Balance von Aktualität und Caching |
On-Demand-Revalidierung
Für kritische Updates (Preisänderungen, Lagerbestände), richteten wir On-Demand-Revalidierung über einen Webhook von unserem Headless-CMS ein:
// pages/api/revalidate.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { secret, slug, locales } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: 'Ungültiges Secret' });
}
try {
const targetLocales = locales || ['en']; // Standard auf Englisch, wenn nicht angegeben
const revalidations = targetLocales.map((locale: string) =>
res.revalidate(`/${locale}/products/${slug}`)
);
await Promise.all(revalidations);
return res.json({ revalidated: true, paths: targetLocales.length });
} catch (err) {
return res.status(500).json({ message: 'Fehler bei Revalidierung' });
}
}
Eine Gotcha: Wenn du ein Produkt revalidierst, das in 30 Locales existiert, machst du 30 Revalidierungs-Anrufe. Für ein Massen-Update von 100 Produkten sind das 3.000 Revalidierungs-Anfragen. Wir mussten Rate-Limiting hinzufügen und diese durch eine Serverless-Funktion in eine Queue stellen, um Vercels API-Limits nicht zu erreichen.
Das Stale-While-Revalidate-Pattern
Die Schönheit von ISR ist, dass es veraltete Content serviert, während es im Hintergrund neu generiert. Für dieses Projekt bedeutete das, dass Benutzer immer eine schnelle Antwort bekamen (gecachter HTML von Vercels Edge), selbst wenn die Daten bis zu 5 Minuten alt waren. Für einen E-Commerce-Shop war dies ein akzeptabler Kompromiss – der Cart- und Checkout-Flow hit immer Live-APIs für echtzeitigen Bestand/Preis.
Content-Pipeline und Headless-CMS-Integration
Der Content lebte in einem Headless-CMS (Contentful, in diesem Fall, obwohl wir ähnliche Setups mit Sanity und Storyblok für andere Clients gemacht haben – siehe unser Headless-CMS-Entwicklungsservices für mehr zu diesem Thema).
Contentfuls Lokalisierungsmodell funktionierte gut für 30 Locales. Jeder Entry hat locale-spezifische Feldwerte, und ihre API unterstützt Abfragen nach Locale. Aber es gibt eine Leistungsüberlegung: das Abrufen eines Produkts mit Daten aller 30 Locales ist erheblich größer als das Abrufen einer Locale.
Wir fragten immer nach einem einzelnen Locale in getStaticProps ab:
const product = await contentfulClient.getEntry(productId, {
locale: mapToContentfulLocale(locale), // 'en-US', 'de-DE', etc.
include: 2, // Löse 2 Ebenen verknüpfter Entries auf
});
Dies hielt API-Antwortzeiten unter 200ms selbst für komplexe Produkteinträge mit mehreren Referenzen.
Translations-Verwaltung
Für UI-Translations (Schaltflächen, Labels, Fehlermeldungen) verwendeten wir Crowdin integriert mit unserem Git-Repo. Der Workflow:
- Entwickler fügen neue englische Strings zu
messages/en/*.jsonhinzu - Crowdin synchronisiert und benachrichtigt Translator
- Translations kommen als PRs zurück
- CI validiert JSON-Struktur und Vollständigkeit
- Fehlende Translations fallen auf Englisch zurück
Die Fallback-Strategie ist entscheidend. Du willst nie eine Production-Seite sehen, die Translations-Keys wie product.add_to_cart anzeigt. Unsere Fallback-Kette war: angeforderte Locale → Sprachfamilie (z.B. pt-BR → pt) → Englisch.
Leistungsergebnisse und Core Web Vitals
Nach dem Launch ist hier, was wir über alle 30 Locales gemessen haben:
| Metrik | Ziel | Aktuell (P75) | Notizen |
|---|---|---|---|
| LCP | < 2.5s | 1.8s | ISR-Cache-Hit |
| FID | < 100ms | 45ms | Minimales Client-seitiges JS |
| CLS | < 0.1 | 0.03 | Font-Loading-Strategie half |
| TTFB | < 800ms | 120ms | Vercel Edge, gecachte Seiten |
| TTFB (Cache-Miss) | < 2s | 1.4s | ISR generiert beim ersten Request |
| Build-Zeit | < 20min | 11min 40s | Nur 8K Seiten vorab generieren |
Die TTFB-Zahlen sind der Star hier. 120ms für gecachte Seiten bedeutet, dass Benutzer in Tokio, São Paulo und Frankfurt alle schnelle Antworten von nahen Edge-Nodes bekommen. Die 1.4s für Cache-Misses ist die ISR-Generierungszeit – akzeptabel, da es nur einmal pro Seite pro Revalidierungs-Periode passiert.
Font-Loading für 30 Sprachen
Eine Leistungs-Herausforderung spezifisch für mehrsprachige Websites: Fonts. Du kannst eine einzelne Font-Familie nicht für 30 Sprachen verwenden. Wir brauchten:
- Latein/Kyrillisch: Inter (meisten europäischen Sprachen)
- Arabisch: Noto Sans Arabic
- CJK: Noto Sans JP/KR/SC/TC
- Thai: Noto Sans Thai
Mit next/font mit per-Locale-Font-Loading verhinderten wir unnötige Font-Downloads. Ein Benutzer, der die japanische Website besucht, lädt nur Noto Sans JP herunter, nicht die arabischen oder Thai-Fonts.
Kostenaufschlüsselung bei Vercel
Lass uns über Geld sprechen, denn hier wird großflächige ISR interessant. Hier ist unser monatliche Vercel-Rechnung Aufschlüsselung in 2025:
| Zeilenelement | Monatliche Kosten | Notizen |
|---|---|---|
| Vercel Pro-Plan | $20/Sitz × 4 | Base-Team-Plan |
| Bandbreite (8TB/mo) | ~$320 | $40/TB nach dem ersten 1TB |
| Serverless-Funktionsausführungen | ~$180 | ISR-Regenerierung + API-Routes |
| Edge-Middleware-Ausführungen | ~$45 | Locale-Erkennung |
| ISR-Schreibvorgänge | ~$90 | Cache-Schreibvorgänge |
| Gesamt | ~$715/mo |
Für eine Website, die über 2 Millionen+ Seitenaufrufe/Monat über 30 Locales handhabet, sind 715 $ pro Monat äußerst vernünftig. Die Alternative – das Ausführen von SSR auf dedizierter Infrastruktur – würde $2.000-4.000/Monat für äquivalente Leistung und Zuverlässigkeit gekostet haben.
Eines zu beachten: ISR-Cache-Schreibkosten können spike, wenn du eine Massen-Revalidierung auslöst. Wir hatten einen Incident, bei dem eine CMS-Massen-Veröffentlichung die Revalidierung für 15.000 Seiten gleichzeitig auslöste. Dieses einzelne Event kostete etwa $40 in zusätzlichen Funktionsausführungen. Wir batchen jetzt Revalidierungs-Anrufe mit einer 100ms-Verzögerung zwischen ihnen.
Fehler, die wir gemacht haben und wie wir sie behoben haben
Ich wäre unehrlich, wenn ich sagen würde, dass dies vom ersten Tag an reibungslos lief. Hier sind die größten Fehler:
Fehler 1: Generierung aller Locales zum Build-Zeitpunkt
Unser erster Ansatz versuchte, jede Seite in jedem Locale vorab zu generieren. Der Build lief 3 Stunden und 47 Minuten. Dann schlug es fehl, da Vercels Build-Timeout (auf Pro) 45 Minuten ist. Selbst nach dem Umzug auf einen benutzerdefinierten Build-Server war der Deploy-Prozess entsetzlich.
Fix: Teilweise Vorab-Generierung mit fallback: 'blocking'. Generiere nur die Seiten, die wichtig sind, lass ISR den langen Schwanz handhaben.
Fehler 2: Nicht das richtige `fallback` gesetzt
Wir verwendeten zunächst fallback: true anstelle von fallback: 'blocking'. Der Unterschied ist wichtig: true serviert einen Skeleton/Loading-Zustand beim ersten Request, während blocking auf die Seiten-Generierung wartet. Mit true bekamen wir Hydrations-Fehler, da unsere Produktkomponenten Daten erwarteten, die beim Fallback-Render noch nicht vorhanden waren.
Fix: Gewechselt zu fallback: 'blocking'. Der erste Besucher einer ungeacheten Seite wartet 1-2 Sekunden, aber jeder danach bekommen die gecachte Version sofort.
Fehler 3: SEO-Hreflang-Tags waren falsch
Das ist eine leichte, es zu vermasseln. Google braucht hreflang-Tags, um die Beziehung zwischen lokalisierten Seiten zu verstehen. Unsere anfängliche Implementierung fehlte das x-default-Tag und hatte Inkonsistenzen zwischen den <link>-Tags und der XML-Sitemap.
// Richtige hreflang-Implementierung
<Head>
{locales.map(loc => (
<link
key={loc}
rel="alternate"
hrefLang={loc}
href={`https://example.com/${loc}${path}`}
/>
))}
<link rel="alternate" hrefLang="x-default" href={`https://example.com/en${path}`} />
</Head>
Fehler 4: Sitemap-Generierung
Mit 91K URLs funktioniert eine einzelne Sitemap-XML-Datei nicht (Googles Limit sind 50.000 URLs pro Sitemap). Wir brauchten einen Sitemap-Index mit mehreren Kind-Sitemaps, aufgeteilt nach Locale:
<!-- sitemap-index.xml -->
<sitemapindex>
<sitemap><loc>https://example.com/sitemaps/en.xml</loc></sitemap>
<sitemap><loc>https://example.com/sitemaps/de.xml</loc></sitemap>
<!-- ... 28 mehr -->
</sitemapindex>
Wir generierten diese mit next-sitemap mit benutzerdefinierten Konfigurationen, und sie werden bei jedem Build regeneriert.
Wann du diesen Stack verwenden solltest (und wann nicht)
Diese Architektur – Next.js + i18n + ISR auf Vercel – ist mächtig, aber es ist nicht die richtige Wahl für alles.
Verwende dies wenn:
- Du 10+ Locales mit Tausenden von Seiten hast
- Content-Updates sind häufig, aber nicht Echtzeit
- Performance und Core Web Vitals Materie für SEO
- Dein Team kennt React/Next.js gut
Erwäge Alternativen wenn:
- Du weniger als 5 Locales und unter 1.000 Seiten hast (einfache SSG könnte einfacher sein)
- Content ist wirklich Echtzeit (Aktienhandel, Live-Scores) – verwende SSR oder Client-seitiges Abrufen
- Du budgetbeschränkt beim Hosting bist – erwäge Astro für rein statische mehrsprachige Websites zu einem Bruchteil der Kosten
- Dein Team ist klein und braucht nicht Reacts Interaktivität – ein statischer Website-Generator mit i18n könnte weniger zu verwalten sein
Für Teams, die ein Projekt wie dieses erwägen, haben wir mehrere Enterprise-Clients geholfen, große Next.js-Anwendungen zu konstruieren und zu bauen. Die Architektur-Entscheidungen in den ersten zwei Wochen bestimmen, ob das Projekt erfolgreich ist oder zu einem Wartungs-Albtraum wird. Wenn du deine spezifische Situation besprechen möchtest, kontaktiere uns.
Häufig gestellte Fragen
Wie funktioniert Next.js i18n-Routing mit ISR?
Next.js i18n-Routing fügt Locale-Präfixe zu URLs hinzu (wie /fr/products/shoes). Bei Kombination mit ISR wird jede Locale + Seite-Kombination unabhängig bei Vercels Edge gecacht. Also /en/products/shoes und /fr/products/shoes sind separate Cache-Einträge, jede mit ihrem eigenen Revalidierungs-Timer. Die getStaticProps-Funktion erhält das Locale in ihrem Kontext, und du holst die entsprechenden Translations und lokalisierten Content dort ab.
Wie viele Seiten kann Next.js ISR maximal auf Vercel handhaben? Es gibt keine harte technische Grenze für die Anzahl der ISR-Seiten, die Vercel servieren kann. Wir haben über 91K Seiten erfolgreich ausgeführt, und ich habe von Projekten mit 500K+ Seiten gehört. Die praktischen Grenzen sind Build-Zeit (für vorab-generierte Seiten), Revalidierungs-Durchsatz und Kosten. Vercels Edge-Cache ist für diesen Umfang konzipiert – es ist im Wesentlichen ein CDN mit intelligenter Ungültigkeitsmachung.
Beeinflusst ISR die SEO für mehrsprachige Websites?
Nein, ISR-Seiten sind vollständig gerenderte HTML, wenn sie aus dem Cache serviert werden, was Suchmaschinen-Crawler sehen. Die wichtigsten SEO-Überlegungen sind richtige hreflang-Tags, eine gut strukturierte Sitemap-Index mit pro-Locale Sitemaps, und sicherstellen, dass deine fallback: 'blocking'-Einstellung verhindert, dass Crawler unvollständige Seiten sehen. Google hat bestätigt, dass ISR/gecachte Seiten gleich wie traditionelle statische HTML behandelt werden.
Wie handhabelst du Translations-Updates ohne Neubereitstellung? Für CMS-verwalteten Content (Produktbeschreibungen, Marketing-Kopie) aktualisieren sich Translations automatisch durch ISR-Revalidierung – entweder auf dem Timer oder über On-Demand-Revalidierungs-Webhooks. Für UI-String-Translations (Button-Labels, Form-Validierungsmeldungen) sind diese zum Build-Zeitpunkt gebündelt, also benötigen sie eine Neubereitstellung. Wir halten diese absichtlich separiert: Content-Änderungen sollten niemals eine Bereitstellung erfordern, aber UI-String-Änderungen gehen durch Code-Review.
Was ist der Kostenunterschied zwischen ISR und SSR für mehrsprachige Websites auf Vercel? SSR führt eine Serverless-Funktion auf jedem einzelnen Request aus. Bei 2M Seitenaufrufen/Monat sind das 2M Funktionsaufrufe bei ungefähr $0,40 pro Million nach dem kostenlosen Tier – etwa $800/Monat nur in Funktionskosten, plus erheblich höhere Bandbreite, da weniger Caching vorhanden ist. ISR serviert die meisten Traffics aus dem Edge-Cache (null Funktionskosten) und ruft Funktionen nur bei Cache-Misses und Revalidierung auf. Unser ISR-Setup kostete etwa $715/Monat insgesamt, während äquivalente SSR $2.500-3.500/Monat gekostet hätte.
Wie handhabelst du unterschiedliche Datums-, Zahlen- und Währungsformate über 30 Locales?
Wir verwenden die integrierte Intl-API des Browsers über next-intls Formatierungs-Utilities. Dies handhabet Datums-Formatierung (Intl.DateTimeFormat), Zahlen-Formatierung (Intl.NumberFormat) und Währungsanzeige korrekt für jedes Locale. Das ICU-Message-Format ermöglicht dir, diese Formatter direkt in Translations-Strings einzubetten: "price": "From {amount, number, ::currency/EUR}". Dies funktioniert Server-seitig während ISR-Generierung und Client-seitig für dynamische Werte.
Sollte ich App Router oder Pages Router für großflächige i18n verwenden?
Seit Next.js 15 (Mitte 2025) hat sich die App-Router-i18n-Story erheblich gereift, und next-intl v4 hat ausgezeichnete App-Router-Unterstützung. Für neue Projekte würde ich App Router empfehlen. Es bietet besseres Streaming, React-Server-Components (die Client-seitiges JavaScript reduzieren) und granularere Cache-Controls. Unser Projekt verwendete Pages Router, da es 2024 gestartet wurde, wenn die App-Router-i18n weniger stabil war, aber ein Greenfield-Projekt heute sollte App Router gehen.
Was passiert, wenn ISR-Revalidierung fehlschlägt? Sehen Benutzer eine Fehlerseite?
Nein, und dies ist eines von ISRs besten Features. Wenn die Revalidierung fehlschlägt (vielleicht ist die CMS-API down, oder es gibt einen Code-Fehler in getStaticProps), serviert Vercel weiterhin die zuletzt erfolgreich generierte Version der Seite. Benutzer sehen nie einen Fehler – sie sehen einfach leicht veralteten Content. Die fehlgeschlagene Revalidierung wird protokolliert, und der nächste Revalidierungs-Versuch wird es wieder versuchen. Dies macht ISR unglaublich resilient im Vergleich zu SSR, wobei ein API-Ausfall sofort zu einem Benutzer-Ausfall wird.