For me the key was to set up my _middleware.ts
the following way:
const PUBLIC_FILE = /\.(.*)$/ // anything having a file extension.
const getIsInternalRoute = (url: string) => {
if (url.startsWith('/_next')) return true // next internal routes
if (url.includes('/api/')) return true // nextjs api routes
return PUBLIC_FILE.test(url) // static files
}
const handleLocaleRedirects = (req: NextRequest) => {
const { pathname } = req.nextUrl
const isPreloadRequest = req.method === 'HEAD'
/*
Due to several bugs with prefetching/middleware/i18n combination
https://github.com/vercel/next.js/issues/35648
https://github.com/vercel/next.js/issues/36100
https://github.com/vercel/next.js/issues/40678
we cannot redirect any prefetch requests. They get cached with the current locale which then causes
infinite loop redirect when we click a link to change the locale.
Possibly might be fixed in later NextJs versions (>=13), but I'd be really careful & skeptical here.
*/
if (isPreloadRequest || getIsInternalRoute(pathname)) return
const locale = req.cookies.NEXT_LOCALE || req.nextUrl.defaultLocale // locale you're supposed to have
if (locale && req.nextUrl.locale !== locale) {
req.nextUrl.locale = locale
return NextResponse.redirect(req.nextUrl)
}
}
export function middleware(req: NextRequest) {
const maybeRedirectResponse = handleLocaleRedirects(req)
if (maybeRedirectResponse) return maybeRedirectResponse
const response = NextResponse.next()
// perhaps other middleware logic here...
return response
}
and set up the switcher link to set the cookie before redirecting to the new route, so the middleware already has the new NEXT_LOCALE value.
function LocaleSwitcher({ locale, children, ...props }) {
const router = useRouter()
const { pathname, asPath, query } = router
const handleClick = e => {
e.preventDefault()
/*
The locale cookie needs to be set before the page redirect, so the nextjs middleware already knows which locale is correct.
*/
setCookie({}, 'NEXT_LOCALE', locale, {
maxAge: 100 * 365 * 24 * 60 * 60, // 100 yrs
})
// change just the locale and maintain all other route information including href's query
router.push({ pathname, query }, asPath, { locale })
}
return (
<Link {...props} locale={locale} to={asPath} onClick={handleClick}>
{children}
</Link>
)
}
This setup checks the user's locale from the cookie on every route and redirects them to the correct locale version of the site.