- Fix next-intl in Next.js 16: Rename middleware to proxy
Fix next-intl in Next.js 16: Rename middleware to proxy
Restore next-intl locale routing after upgrading to Next.js 16: rename middleware.ts→proxy.ts, add provider, and…

In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.
If you upgraded to Next.js 16 and suddenly your locale routing stopped working, you're in the right place. No redirects to /en, wrong locale in server components, or this in your logs:
Error: Unable to find `next-intl` locale because the middleware didn't run on this request.
See https://next-intl.dev/docs/routing/middleware#unable-to-find-locale
The cause is a single breaking change in Next.js 16: middleware.ts was renamed to proxy.ts. Your next-intl setup is fine — the file Next.js is looking for just no longer exists.
This guide covers the exact fix, why Next.js made this change, and two additional next-intl 4.0 breaking changes that tend to surface in the same upgrade.
Next.js renamed the file to clarify what it actually does. From the release notes:
"proxy.ts replaces middleware.ts and makes the app's network boundary explicit. proxy.ts runs on the Node.js runtime."
The old name caused confusion with Express-style middleware and made the feature sound more general-purpose than it is. The new name makes it clear: this file sits at the network boundary and proxies requests before they reach your app. The logic inside hasn't changed at all — it's a rename, not a rewrite.
Next.js 16 still accepts middleware.ts for now but treats it as deprecated and logs a warning:
You are using the `middleware` file convention, which is deprecated and has been renamed to `proxy`.
When next-intl's locale negotiation depends on that file running — and it does — a deprecated or ignored file means no locale gets set, which is why you see the "middleware didn't run" error.
src/middleware.ts → src/proxy.ts
That's it for most next-intl setups. If you're using createMiddleware with a default export, the file contents stay identical:
// File: src/proxy.ts
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
};
The next-intl docs confirm it: "proxy.ts was called middleware.ts up until Next.js 16." No changes to imports, no changes to createMiddleware, no changes to your routing config.
If you wrote a custom wrapper around createMiddleware rather than using a default export, Next.js 16 expects the named export to be called proxy, not middleware:
// File: src/proxy.ts — before
import type {NextRequest} from 'next/server';
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
const intlMiddleware = createMiddleware(routing);
export function middleware(request: NextRequest) {
return intlMiddleware(request);
}
export const config = {
matcher: '/((?!api|_next|_vercel|.*\\..*).*)',
};
// File: src/proxy.ts — after
import type {NextRequest} from 'next/server';
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
const intlProxy = createMiddleware(routing);
export function proxy(request: NextRequest) {
return intlProxy(request);
}
export const config = {
matcher: '/((?!api|_next|_vercel|.*\\..*).*)',
};
If you want to automate this, Next.js ships a codemod that handles both the file rename and the function rename in one command:
npx @next/codemod@canary middleware-to-proxy .
No changes required to next.config.ts, src/i18n/routing.ts, src/i18n/navigation.ts, or src/i18n/request.ts. The proxy file is the only thing that moves.
If you're upgrading to Next.js 16 you're likely also moving from next-intl v3 to v4. Two other changes in that upgrade break silently in ways that look similar to the proxy issue.
In next-intl v3, NextIntlClientProvider was optional in many setups. In v4, if any client component in your tree calls useTranslations, a provider must exist above it or you'll hit:
Error: Failed to call `useTranslations` because the context from `NextIntlClientProvider` was not found.
The fix is straightforward — add it to your locale layout with no props:
// File: src/app/[locale]/layout.tsx
import {NextIntlClientProvider} from 'next-intl';
export default async function LocaleLayout({children}: {children: React.ReactNode}) {
return (
<html>
<body>
<NextIntlClientProvider>
{children}
</NextIntlClientProvider>
</body>
</html>
);
}
In v4, NextIntlClientProvider automatically inherits messages and formats from your i18n/request.ts — you don't need to pass them as props. If you were previously passing them manually, you can remove those props entirely.
In v3, returning locale from getRequestConfig was optional. In v4 it's required. If you're missing it, next-intl can't determine which locale is active, which compounds the proxy issue:
// File: src/i18n/request.ts
import {getRequestConfig} from 'next-intl/server';
import {hasLocale} from 'next-intl';
import {routing} from './routing';
export default getRequestConfig(async ({requestLocale}) => {
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested)
? requested
: routing.defaultLocale;
return {
locale, // required in v4 — don't omit this
messages: (await import(`../../messages/${locale}.json`)).default,
};
});
The locale field in the return object is what next-intl uses downstream. Without it, you'll see the same "Unable to find next-intl locale" error even after correctly renaming middleware.ts to proxy.ts.
The Next.js 16 upgrade breaks next-intl locale routing through a combination of three changes: the middleware.ts → proxy.ts rename (Next.js-level), the required NextIntlClientProvider wrapper (next-intl 4.0), and the mandatory locale return from getRequestConfig (next-intl 4.0). Any one of them is enough to produce the "Unable to find next-intl locale" error, which is why the upgrade can feel hard to diagnose.
The fixes are all mechanical — rename a file, add a provider, add one field to a return object. Nothing about how next-intl works has changed, and your routing configuration stays exactly as it was.
Let me know in the comments if you're hitting anything else after the upgrade, and subscribe for more practical development guides.
Thanks, Matija