Next.js internationalized site error - Too many redirects

I’ve redeployed a Next.js 13 site that was working so far. Now I get a “too many redirects” error. And I think it has to do with the middleware I added now.

In the middleware I’m redirecting based on the page locale, like this:

if (defaultLocale === locale) {
// we remove locale if pathname contains default locale, by redirecting
if (pathname.startsWith(`/${locale}/`)) {
// if pathname has subpath, remove "/locale", eg. from "/ca/something" to "/something"
return NextResponse.redirect(new URL(pathname.replace(`/${locale}`, ""), req.url));
} else {
// if it doesn't, remove "locale", eg. from "/ca to "/"
return NextResponse.redirect(new URL(pathname.replace(`${locale}`, ""), req.url));
}
}

But this seems to run into trouble and trigger and infinite redirect loop. In my local setup it works well, though. Are you aware of something I might be doing wrong?

Something else that might be relevant is that I believe the middleware should be handled by edge functions (by default) and it doesn’t seem to be the case, because when I check the logs, nothing appears on edge functions, but it does on functions.

And on the deploy logs I’m seeing “/.netlify/functions/___netlify-handler” for most routes.
Hopefully you can point me in the right direction. Thanks a lot!

Is there a site we can check?

Yes,

Here’s a test website where the error is occuring: https://visionary-beignet-9da304.netlify.app/
And here’s the corresponding public repo: GitHub - marnauortega/internationalized-test

Hi @hrishikesh,
Is there any way you could take a look into it?

Thanks a lot!

I don’t think this is a Netlify issue. Looks like you’re deliberately causing this issue through your code. For example, I can see you’re checking for defaultLocale, which based on your code is ca. en is not the default, but a valid locale, so I simply tried visiting: 500: Internal Server Error (visionary-beignet-9da304.netlify.app) and it works fine. It gets a HTTP 500 error which is being discussed here: Server.edge not defined Error on nextJS SSR functions cause site to return 500 Errors.

But about the too many redirects, you’re redirecting from / to /ca: internationalized-test/middleware.js at main · marnauortega/internationalized-test (github.com). Then it runs the middleware again and then it checks internationalized-test/middleware.js at main · marnauortega/internationalized-test (github.com) again. This cycle continues over and over.

Hi @hrishikesh,

Thanks a lot for looking into it. You were right that I was suffering from that 500 error. Now that is solved.

But I’m still in trouble with the too many redirects. And I’m not sure it’s a problem with my code. let me explain:

In my local setup it works fine, and when I upload to vercel it does too.
My rationale for why it should work is the following:

When we visit https://visionary-beignet-9da304.netlify.app/ca it should, as you said, be redirected to https://visionary-beignet-9da304.netlify.app according to line 20.
Then the middleware will rerun, and this time, when it gets to line 33, it will rewrite the url in the following way: the browser url will show https://visionary-beignet-9da304.netlify.app but the page shown will correspond to https://visionary-beignet-9da304.netlify.app/ca.
But here comes the problem, I think this rewrite shouldn’t retrigger middleware, and thus shouldn’t cause an infinite loop. And in the local setup and in vercel it doesn’t, and so it works well. You can check my vercel deployment and see that it works fine here: https://internationalized-test.vercel.app
But in Netlify it does rerun the middleware and cause the issue.

I don’t know if this is related to this: fix: preserve original path after middleware rewrite by pieh · Pull Request #2177 · netlify/next-runtime · GitHub

If you could take a quick look into it and see if it’s an issue with Netlify, I would really appreciate, thanks!

You’re using NextResponse.redirect() and its behaviour is to redirect using a 3xx redirect. If you wish to rewrite, you should use NextResponse.rewrite method.

However, I’d rather recommend to check the x-middleware-rewrite header on a request. If a request has that header, it would mean that it has already been redirected once and should not be processed further.

Thank you Hrishikesh. I’m using both rewrite and redirect in my code. And I still don’t understand why it doesn’t work in Netlify while it works in Vercel and local.

But I’d try to fix it using the x-middleware-rewrite. I just don’t know how to implement that in the middleware. If you could tell me briefly how to do this or point me to some documentation, it would be really helpful.

@marnau I’m not seeing NextResponse.rewrite() in the code you’ve shared. If you’ve attempted that somewhere please provide us links to that attempt so we can check.

Regarding the header, you can simply use NextRequest.headers.get('x-middleware-rewrite'). If it exists, redirect has already been done so skip processing, if not (null value), redirect.

Thanks ofr helping, @hrishikesh.

I did use NextResponse.rewrite() on line 37 of middleware: https://github.com/marnauortega/internationalized-test/blob/f985c4e414a56ecb8f6fd2c18a3242c56f9093b5/middleware.js#L37

I’m trying to implement your fix. But NextRequest.headers.get(‘x-middleware-rewrite’) comes null all the time, I don’t know why. I can see the x-middleware-rewrite appearing in my network dev tools, but I can’t see it appearing in any logs in edge functions. Can you see if I’m doing something wrong?

Thanks a lot!

Sorry for the back and forth. I actually found an old issue which could be related here. The solution in that issue was:

Found out the issue here is that Next’s redirects don’t work with just plain middleware and our edge functions, however we do have this functionality supported with our advanced middleware package.

Next.js Middleware on Netlify | Netlify Docs

The steps required to get this working:

  1. Install @netlify/next:
    npm i @netlify/next
  2. Change their middleware file to import MiddlewareRequest from @netlify/next, get our middleware request object by passing in Next’s request, and then returning the rewrite from our middleware. See changes I made to their middleware.ts file below:
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

import { MiddlewareRequest } from '@netlify/next'                                     // Add this

export function middleware(request: NextRequest) {
  const { pathname, searchParams } = request.nextUrl;

  const middlewareRequest = new MiddlewareRequest(request)                            // Add this

  const cookie = request.cookies.get("customer_group");
  const isBGroup = cookie === "other" || searchParams.get("group") === "other";
  const otherUrl = new URL("/other", request.url);
  console.log({
    url: request.url,
    pathname: pathname.toString(),
    searchParams: searchParams.toString(),
    cookie,
    isBGroup,
    pathnameMatch: ["/", "/en-US"].includes(pathname.toString()),
    locale: request.nextUrl.locale.toString(),
    defaultLocale: request.nextUrl.defaultLocale?.toString(),
    domainLocale: request.nextUrl.domainLocale?.toString(),
    
  });

  if (isBGroup && ["/", "/en-US"].includes(pathname.toString())) {
    console.log("rewrite");
    // return NextResponse.rewrite(otherUrl)   
    return middlewareRequest.rewrite(otherUrl)                                    // Change the above to this
  } else if (pathname === "/other") {
    console.log("404");
    // return NextResponse.rewrite(new URL("/404", request.url));
  }
  console.log("else");

  return NextResponse.next();
}

export const config = {
  matcher: ["/", "/other", "/en-US"],
};

Could you see if a similar thing works for your use case?

Thanks a lot, @hrishikesh!

I finally managed to make it work.
I did use MiddlewareRequest, as you suggested.
I was bumping into a problem when the url to rewrite contained a trailing slash, so I got rid of it.
And I made sure to exclude the _ipx folder from middleware to avoid images getting rewritten.

This is my final code:

import { NextResponse } from "next/server";
import { locales as localesArray } from "./lib/locales";
import { MiddlewareRequest } from "@netlify/next";

let locales = localesArray.map((language) => language.lang);
const defaultLocale = locales[0];

export async function middleware(req) {
  const pathname = req.nextUrl.pathname;
  const searchParams = req.nextUrl.search;
  const locale = pathname.split("/")[1];

  const middlewareRequest = new MiddlewareRequest(req);

  // Check if the default locale is in the pathname
  if (defaultLocale === locale) {
    console.log("remove default locale");
    // we remove locale if pathname contains default locale, by redirecting
    if (pathname.startsWith(`/${locale}/`)) {
      // if pathname has subpath, remove "/locale", eg. from "/ca/something" to "/something"
      return NextResponse.redirect(new URL(`${pathname.replace(`/${locale}`, "")}${searchParams}`, req.url));
    } else {
      // if it doesn't, remove "locale", eg. from "/ca to "/"
      return NextResponse.redirect(new URL(`${pathname.replace(`${locale}`, "")}${searchParams}`, req.url));
    }
  }

  const pathnameIsMissingValidLocale = locales.every((locale) => {
    return !pathname.startsWith(`/${locale}`);
  });

  if (pathnameIsMissingValidLocale) {
    // Remove trailing slash
    if (pathname === "/") pathname = "";
    // we rewrite pathnames without valid locale: visit `/ca`, but browser url shows `/`
    // Incoming request: "/hello"
    // Rewritten request: page shown "/en/hello", browser url "/hello"
    return middlewareRequest.rewrite(
      // Note: Pathname already includes "/"
      new URL(`/${defaultLocale}${pathname}${searchParams}`, req.url)
    );
  }
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - favicon.ico (favicon file)
     */
    "/((?!api|admin|_next/static|_next/image|_ipx|assets|favicon.ico|under-development.svg|public).*)",
  ],
};

Wow this is awesome! Thank you for your detailed response this will really help individuals who stumble upon this thread in the future.

1 Like