Next.js Discord

Discord Forum

Why is my middleware (proxy.ts) executed 3 times for a single page load?

Unanswered
California pilchard posted this in #help-forum
Open in Discord
California pilchardOP
Hi!
I’m using Next.js 16 + App Router + Turbopack, and I’ve noticed that my middleware (proxy.ts) runs 3 times when I load a single page (just one refresh).

There is my proxy.ts :
const intlMiddleware = createIntlMiddleware(routing);

export async function proxy(request: NextRequest) {
  const start = performance.now();

  let response = intlMiddleware(request);

  const [, locale, ...rest] = new URL(
    response.headers.get('x-middleware-rewrite') || request.url
  ).pathname.split('/');
  const pathname = '/' + rest.join('/');

  console.log('[proxy] ', { locale, pathname });

  const supabase = createMiddlewareClient({ request, response });
  const {
    data: { user },
  } = await supabase.auth.getUser();
  /**
   * Redirect user if not logged in
   */
  if (user && siteConfig.routes.anonRoutes.some((path) => pathname.startsWith(path))) {
    return NextResponse.redirect(
      new URL(`/${locale}`, request.url)
    );
  }
  /**
   * Redirect user if logged in
   */
  if (!user && siteConfig.routes.authRoutes.some((path) => pathname.startsWith(path))) {
    const redirectTo = encodeURIComponent(request.nextUrl.pathname);
    return NextResponse.redirect(
      new URL(`/${locale}/auth/login?redirect=${redirectTo}`, request.url)
    );
  }
  return (response);
}

export const config = {
  matcher: [ `/((?!api|_next|_vercel|favicon\\.ico|manifest\\.webmanifest|robots\\.txt|sitemaps/|opensearch\\.xml|.well-known/|assets/|.*\\.(?:json|xml|js)$).*)`],
};


If you have any idea, thx a lot ! Because right now my app is really, reallyyyyyy slow

15 Replies

@California pilchard Hi! I’m using Next.js 16 + App Router + Turbopack, and I’ve noticed that my middleware (proxy.ts) runs 3 times when I load a single page (just one refresh). There is my `proxy.ts` : tsx const intlMiddleware = createIntlMiddleware(routing); export async function proxy(request: NextRequest) { const start = performance.now(); let response = intlMiddleware(request); const [, locale, ...rest] = new URL( response.headers.get('x-middleware-rewrite') || request.url ).pathname.split('/'); const pathname = '/' + rest.join('/'); console.log('[proxy] ', { locale, pathname }); const supabase = createMiddlewareClient({ request, response }); const { data: { user }, } = await supabase.auth.getUser(); /** * Redirect user if not logged in */ if (user && siteConfig.routes.anonRoutes.some((path) => pathname.startsWith(path))) { return NextResponse.redirect( new URL(`/${locale}`, request.url) ); } /** * Redirect user if logged in */ if (!user && siteConfig.routes.authRoutes.some((path) => pathname.startsWith(path))) { const redirectTo = encodeURIComponent(request.nextUrl.pathname); return NextResponse.redirect( new URL(`/${locale}/auth/login?redirect=${redirectTo}`, request.url) ); } return (response); } export const config = { matcher: [ `/((?!api|_next|_vercel|favicon\\.ico|manifest\\.webmanifest|robots\\.txt|sitemaps/|opensearch\\.xml|.well-known/|assets/|.*\\.(?:json|xml|js)$).*)`], }; If you have any idea, thx a lot ! Because right now my app is really, reallyyyyyy slow
The proxy runs before every request. Also for fonts, images, videos, documents, pages, layouts, ... Every requests. And that means it will be executed before it.

Because right now my app is really, reallyyyyyy slow
the proxy is not meant to do heavy stuff. Fetching user data or stuff like that should be done somewhere else (for example in the page.tsx)

If you explicity want to know which request triggers it, log the pathname

So:
1. Identify which pathname was requests (and maybe eliminate those requests)
2. Remove heavy tasks from the proxy
@B33fb0n3 The proxy runs before *every* request. Also for fonts, images, videos, documents, pages, layouts, ... **Every** requests. And that means it will be executed before it. > Because right now my app is really, reallyyyyyy slow the proxy is *not* meant to do heavy stuff. Fetching user data or stuff like that should be done somewhere else (for example in the page.tsx) If you explicity want to know *which* request triggers it, log the pathname So: 1. Identify which pathname was requests (and maybe eliminate those requests) 2. Remove heavy tasks from the proxy
California pilchardOP
But the documentation from Supabase say to set the check of session inside middleware to be sure its secure. I also have a matcher which exclude a lot of stuff and the pathname is the same multiple time, for example :
[proxy] pathname '/'
[proxy] pathname '/'

Ive remove some stuff on my middleware and now is way faster. Rn Im having a big miss understanding why my layout.tsx (the root one) is re-rendered each time a change pas...

I have :
src/app
├── [lang]
│   ├── (app)
│   ├── layout.tsx
│   ├── opengraph-image.jpg
│   └── twitter-image.jpg

And inside my layout.tsx I have :
import '@/styles/globals.css';
import { Metadata, Viewport } from 'next';
import { siteConfig } from '@/config/site';
import { fontSans } from '@/lib/fonts';
import { cn } from '@/lib/utils';
import Providers from '@/context/Providers';
import Script from 'next/script';
import { getLangDir } from 'rtl-detect';
import { routing, seoLocales } from '@/lib/i18n/routing';

/*
...metadata
*/

export default async function LangLayout({
  children,
  params
} : {
  children: React.ReactNode;
  params: Promise<{ lang: string }>;
}) {
  const startTime = Date.now();
  const { lang } = await params;
  const direction = getLangDir(lang);
  console.log(`[LAYOUT] [${lang}] layout.tsx loaded in ${Date.now() - startTime}ms`);
  return (
    <html lang={lang} dir={direction} suppressHydrationWarning>
      <head>
        <link rel="search" type="application/opensearchdescription+xml" title="Recomend" href="/opensearch.xml" />
      </head>
      {process.env.NODE_ENV === 'production' ? (
        <Script
        defer
        src={process.env.ANAYLTICS_URL}
        data-website-id={process.env.ANALYTICS_ID}
      />) : null}
      <body className={cn('font-sans antialiased', fontSans.variable)}>
        <Providers locale={lang}>{children}</Providers>
      </body>
    </html>
  );
}
@California pilchard But the documentation from Supabase say to set the check of session inside middleware to be sure its secure. I also have a matcher which exclude a lot of stuff and the pathname is the same multiple time, for example : [proxy] pathname '/' [proxy] pathname '/' Ive remove some stuff on my middleware and now is way faster. Rn Im having a big miss understanding why my `layout.tsx` (the root one) is re-rendered each time a change pas... I have : src/app ├── [lang] │ ├── (app) │ ├── layout.tsx │ ├── opengraph-image.jpg │ └── twitter-image.jpg And inside my `layout.tsx` I have : tsx import '@/styles/globals.css'; import { Metadata, Viewport } from 'next'; import { siteConfig } from '@/config/site'; import { fontSans } from '@/lib/fonts'; import { cn } from '@/lib/utils'; import Providers from '@/context/Providers'; import Script from 'next/script'; import { getLangDir } from 'rtl-detect'; import { routing, seoLocales } from '@/lib/i18n/routing'; /* ...metadata */ export default async function LangLayout({ children, params } : { children: React.ReactNode; params: Promise<{ lang: string }>; }) { const startTime = Date.now(); const { lang } = await params; const direction = getLangDir(lang); console.log(`[LAYOUT] [${lang}] layout.tsx loaded in ${Date.now() - startTime}ms`); return ( <html lang={lang} dir={direction} suppressHydrationWarning> <head> <link rel="search" type="application/opensearchdescription+xml" title="Recomend" href="/opensearch.xml" /> </head> {process.env.NODE_ENV === 'production' ? ( <Script defer src={process.env.ANAYLTICS_URL} data-website-id={process.env.ANALYTICS_ID} />) : null} <body className={cn('font-sans antialiased', fontSans.variable)}> <Providers locale={lang}>{children}</Providers> </body> </html> ); }
Date.now() can be often a cause. Some behavior is also different in production and developement.

Great you cleanup your proxy. Can you test is once in production and maybe without the Date.now()?
@B33fb0n3 `Date.now()` can be often a cause. Some behavior is also different in production and developement. Great you cleanup your proxy. Can you test is once in production and maybe without the `Date.now()`?
California pilchardOP
Okay I only have one middleware call now ! My middleware take 51ms. But its very weird because intlMiddleware (from next-intl) take 50ms which is hugeeeee. Ive created a new NextJS app to test, and the intlMiddleware only take 0.4ms so my last fix should be with next-intl but I have no idea what is wrong....

In my app I have (NextJS 16) :
const intlMiddleware = createIntlMiddleware(routing);

export async function proxy(request: NextRequest) {
  const startTime = performance.now();
  const response = intlMiddleware(request);
  console.log(`Intl middleware processed in ${performance.now() - startTime} ms`);
  // ...
};

And my test app is the same (NextJS 15) :
const intlMiddleware = createMiddleware(routing);
export default async function middleware(req: NextRequest) {
  const start = performance.now();
  const res = intlMiddleware(req);
  const duration = performance.now() - start;
  console.log(`Intl middleware took ${duration.toFixed(2)} ms`);
  return res;
}

Its basically the same...
California pilchardOP
Ive upgraded my test app to NextJS 16 and she still around 1.5ms. Its really weird because I have the exact same routing setup for next-intl in both app
I guess 1.5ms is pretty solid, dont you think so?
@B33fb0n3 I guess 1.5ms is pretty solid, dont you think so?
California pilchardOP
No, sorry, I probably expressed myself badly, but the test app actually works very well with the intl middleware running in 1.5 ms, while on the app I’m developing (the one we’ve been talking about from the start), the intl middleware takes 150 ms to run.
Just to get things clear:
- one test app with next15 => fast, but with same intl code compared its bad
- one test app with next16 => super fast
- your app with next15 first => pretty long loading time
- your app with next16 now => shorter, but still 150ms

I can't follow you right now where the problem is right now? Like that it takes 150ms in your dev env?
California pilchardOP
The test app only measures the intlMiddleware, not the other middleware issues I had before. That test app runs the intlMiddleware in ~0.5 ms.

Both apps are on Next 16. My main app hits ~150 ms for the intlMiddleware. I found the root cause: my main app supports 140 locales. When I set the same number of locales in the test app, it also reaches ~150 ms.
So I guess I cant fix it
@B33fb0n3 ohh you dont use negotiator to route the client to the correct locale and like that the `intlMiddleware` loads all dictionaries and then redirects to the correct one?
California pilchardOP
I’m using next-intl, so I’m not fully in control of how the locale detection works internally. My middleware is basically just:
const intlMiddleware = createIntlMiddleware(routing);
const response = intlMiddleware(request);


My routing is just the list of supported locales (140) with a default locale :
export const routing = defineRouting({
  // A list of all locales that are supported
  locales: supportedLocales,
  // Used when no locale matches
  defaultLocale: defaultLocale,
  // Prefix for all locale-aware routes
  localePrefix: 'as-needed',
});

I’m not sure whether next-intl loads all dictionaries during detection or not, but based on the timings it looks like it might. I don’t see any place in the API to check or override that behavior.