Subpath Routes via Middleware + Dictionaries
Internationalization
App Router does not ship a built-in i18n config. The recommended pattern is middleware-based locale detection plus per-locale dictionaries loaded in the route.
What you'll learn
- Detect the locale in `middleware.ts`
- Redirect to `/[lang]/...`
- Load a dictionary per route
Next 13’s i18n config did not survive the App Router migration. The official pattern now
is to handle locales yourself with middleware and a [lang] dynamic segment.
Detect the Locale
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
const LOCALES = ['en', 'fr', 'es']
const DEFAULT = 'en'
function pickLocale(req: NextRequest) {
const header = req.headers.get('accept-language') ?? ''
for (const tag of header.split(',')) {
const lang = tag.split(';')[0].split('-')[0].trim()
if (LOCALES.includes(lang)) return lang
}
return DEFAULT
}
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl
if (LOCALES.some((l) => pathname.startsWith(`/${l}/`) || pathname === `/${l}`)) {
return NextResponse.next()
}
const locale = pickLocale(req)
return NextResponse.redirect(new URL(`/${locale}${pathname}`, req.url))
}
export const config = { matcher: ['/((?!_next|api|favicon).*)'] } The Lang Segment
// app/[lang]/page.tsx
import { getDictionary } from './dictionaries'
export default async function Page({
params,
}: {
params: Promise<{ lang: string }>
}) {
const { lang } = await params
const t = await getDictionary(lang)
return <h1>{t.welcome}</h1>
} Dictionaries
// app/[lang]/dictionaries.ts
import 'server-only'
const dictionaries = {
en: () => import('./dictionaries/en.json').then((m) => m.default),
fr: () => import('./dictionaries/fr.json').then((m) => m.default),
es: () => import('./dictionaries/es.json').then((m) => m.default),
}
export const getDictionary = (lang: string) =>
dictionaries[lang as keyof typeof dictionaries]?.() ?? dictionaries.en() For a richer setup (plurals, dates, currencies), reach for next-intl — it builds on
this same pattern and adds the formatters.