- Next.js
- Enterprise Architecture & System Design
- Internationalization (i18n): App Router Routing
Internationalization (i18n): App Router Routing
Unlike Pages Router which featured built-in i18n configuration in next.config.js, App Router relies on subpath routing via dynamic route segments (app/[lang]/...) orchestrated by proxy.ts or middleware.ts. The proxy negotiates language preferences from Accept-Language headers or cookies and rewrites/redirects to the appropriate locale.
Learning Objectives
Implement localized subpath routing using the [lang] dynamic segment convention.
Detect and negotiate user locales using Accept-Language headers and negotiator/formatjs.
Persist language preferences across user sessions via cookies.
Generate localized static routes with generateStaticParams for all supported locales.
Prerequisites & Background Knowledge
Why Interviewers Care
Required for all global applications expanding into international markets across North America, Europe, Asia, and LATAM.
The Challenge
How does internationalization (i18n) work in Next.js App Router without next.config.js i18n support? Implement locale negotiation in Middleware and define a localized [lang] layout.
Code Architecture & Implementation
1// middleware.ts2import { NextRequest, NextResponse } from 'next/server';34const locales = ['en', 'es', 'fr', 'de'];5const defaultLocale = 'en';67function getLocale(request: NextRequest): string {8 // 1. Check user cookie first9 const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;10 if (cookieLocale && locales.includes(cookieLocale)) return cookieLocale;1112 // 2. Parse Accept-Language header13 const acceptLang = request.headers.get('accept-language') || '';14 const preferred = acceptLang.split(',')[0].split('-')[0].toLowerCase();1516 return locales.includes(preferred) ? preferred : defaultLocale;17}1819export function middleware(request: NextRequest) {20 const { pathname } = request.nextUrl;2122 // Check if pathname already starts with a supported locale23 const pathnameHasLocale = locales.some(24 (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`25 );2627 if (pathnameHasLocale) return NextResponse.next();2829 // Redirect to localized URL: /products -> /en/products30 const locale = getLocale(request);31 request.nextUrl.pathname = `/${locale}${pathname}`;32 return NextResponse.redirect(request.nextUrl);33}
Architectural Explanation
App Router delegates internationalization to folder-based routing: app/[lang]/page.tsx. Every route is nested inside the [lang] dynamic segment. When users arrive at /about, Middleware detects their browser language (e.g. Spanish) and redirects them to /es/about. Component dictionaries (e.g. getDictionary(lang)) load strictly server-side JSON translation files, meaning only the translations for the active language are loaded without shipping huge multilingual JSON bundles to the browser.
Common Mistakes & Anti-Patterns
Attempting to use `i18n` in next.config.js in App Router and wondering why it has no effect.
Shipping translation dictionaries for ALL languages in the client JavaScript bundle.
Not including `hreflang` alternate links in metadata, harming international SEO rankings.
Interview Follow-Up Traps
The interviewer asks: "How do you avoid shipping unused language strings to the browser?" Keep translation dictionaries inside Server Components; only pass the translated strings as props to Client Components.
Frequently Asked Questions
How do you switch languages dynamically?
Set a `NEXT_LOCALE` cookie and navigate to the corresponding localized subpath URL.
How does generateStaticParams handle multiple languages?
Return arrays pairing locales with slugs: `[{ lang: "en", slug: "about" }, { lang: "es", slug: "about" }]`.
