BuildWithMatija
Get In Touch
  1. Home
  2. Blog
  3. Next.js
  4. 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…

19th March 2026·Updated on:6th April 2026·MŽMatija Žiberna·
Next.js
Fix next-intl in Next.js 16: Rename middleware to proxy

⚡ Next.js Implementation Guides

In-depth Next.js guides covering App Router, RSC, ISR, and deployment. Get code examples, optimization checklists, and prompts to accelerate development.

No spam. Unsubscribe anytime.

Related Posts:

  • •How to Fix "Couldn't find next-intl config file" Error in Next.js 15
  • •How to Use Canonical Tags and Hreflang in Next.js 16
  • •next-intl Guide: Add i18n to Next.js 16 (Complete Setup)

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.

Why Next.js 16 Renamed middleware.ts to proxy.ts

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.

The Fix: Three Steps

Step 1: Rename the file

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.

Step 2: Rename the exported function (custom setups only)

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 .

Step 3: Verify nothing else needs changing

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.

Two More Breaking Changes Worth Checking

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.

NextIntlClientProvider is now required

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.

getRequestConfig must return locale explicitly

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.

Wrapping Up

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

📄View markdown version
0

Frequently Asked Questions

Comments

Leave a Comment

Your email will not be published

Stay updated! Get our weekly digest with the latest learnings on NextJS, React, AI, and web development tips delivered straight to your inbox.

10-2000 characters

• Comments are automatically approved and will appear immediately

• Your name and email will be saved for future comments

• Be respectful and constructive in your feedback

• No spam, self-promotion, or off-topic content

No comments yet

Be the first to share your thoughts on this post!

Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in

How to Fix "Couldn't find next-intl config file" Error in Next.js 15
How to Fix "Couldn't find next-intl config file" Error in Next.js 15

23rd September 2025

How to Use Canonical Tags and Hreflang in Next.js 16
How to Use Canonical Tags and Hreflang in Next.js 16

6th September 2025

next-intl Guide: Add i18n to Next.js 16 (Complete Setup)
next-intl Guide: Add i18n to Next.js 16 (Complete Setup)

5th September 2025

Table of Contents

  • Why Next.js 16 Renamed middleware.ts to proxy.ts
  • The Fix: Three Steps
  • Step 1: Rename the file
  • Step 2: Rename the exported function (custom setups only)
  • Step 3: Verify nothing else needs changing
  • Two More Breaking Changes Worth Checking
  • NextIntlClientProvider is now required
  • getRequestConfig must return locale explicitly
  • Wrapping Up
On this page:
  • Why Next.js 16 Renamed middleware.ts to proxy.ts
  • The Fix: Three Steps
  • Two More Breaking Changes Worth Checking
  • Wrapping Up
Build With Matija Logo

Build with Matija

Modern websites, content systems, and AI workflows built for long-term growth.

Services

  • Headless CMS Websites
  • Next.js & Headless CMS Advisory
  • AI Systems & Automation
  • Website & Content Audit
  • Resources

    • Case Studies
    • How I Work
    • Blog
    • CMS Hub
    • E-commerce Hub
    • Dashboard

    Headless CMS

    • Payload CMS Developer
    • CMS Migration
    • Payload vs Sanity
    • Payload vs WordPress
    • Payload vs Contentful

    Get in Touch

    Ready to modernize your stack? Let's talk about what you're building.

    Book a discovery callContact me →
    © 2026BuildWithMatija•All rights reserved