Why is my middleware (proxy.ts) executed 3 times for a single page load?
Unanswered
California pilchard posted this in #help-forum
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
If you have any idea, thx a lot ! Because right now my app is really, reallyyyyyy slow
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.
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
Because right now my app is really, reallyyyyyy slowthe 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 :
Ive remove some stuff on my middleware and now is way faster. Rn Im having a big miss understanding why my
I have :
And inside my
[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.jpgAnd 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}
/>
);
}@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
In my app I have (NextJS 16) :
And my test app is the same (NextJS 15) :
Its basically the same...
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 pilchard 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) :
tsx
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) :
tsx
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...
yea, next16 might change stuff so it loads faster. Normally every major upgrade makes everything faster. And in this case they might not only changed the name from middleware to proxy, but maybe also how things work internally
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?
- 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
Both apps are on
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
@California pilchard 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.
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?@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
My routing is just the list of supported locales (140) with a default locale :
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.
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.
@California pilchard I’m using `next-intl`, so I’m not fully in control of how the locale detection works internally. My middleware is basically just:
tsx
const intlMiddleware = createIntlMiddleware(routing);
const response = intlMiddleware(request);
My routing is just the list of supported locales (140) with a default locale :
tsx
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.
What do you think about using negotiator to prefix the route with your locale instead of using the middleware form next intl?
You can find one example of it inside the nextjs docs. You don’t need anything additional, just a bit of code in your middleware
You can find one example of it inside the nextjs docs. You don’t need anything additional, just a bit of code in your middleware
@B33fb0n3 What do you think about using negotiator to prefix the route with your locale instead of using the middleware form next intl?
You can find one example of it inside the nextjs docs. You don’t need anything additional, just a bit of code in your middleware
California pilchardOP
I've just try using negociator, and its wayyy faster... why ? I means how a big lib like
next-intl is that slow ?