QuiiBz / next-international

Type-safe internationalization (i18n) for Next.js
https://next-international.vercel.app
MIT License
1.2k stars 52 forks source link

Request header loss when chaining I18nMiddleware with other middleware #341

Open Tatamo opened 5 months ago

Tatamo commented 5 months ago

Describe the bug Request headers added by other middleware are being lost when I18nMiddleware is used.

To set request header in middleware, we need to pass modified header toNextReponse.next() and NextResponse.rewrite() but they are called inside of I18nMiddleware.

To Reproduce Just following app router setup https://next-international.vercel.app/docs/app-setup and setting request header document https://nextjs.org/docs/app/building-your-application/routing/middleware#setting-headers .

// middleware.ts
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { createI18nMiddleware } from "next-international/middleware";

const I18nMiddleware = createI18nMiddleware({
  locales: ["en", "fr"],
  defaultLocale: "en"
});

export function middleware(request: NextRequest) {
  // Clone the request headers and set a new header `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set("x-hello-from-middleware1", "hello");

  // You can also set request headers in NextResponse.rewrite
  const response = NextResponse.next({
    request: {
      // New request headers
      headers: requestHeaders
    }
  });

  // cannot pass requestHeaders argument to NextResponse.next() or NextResponse.rewrite() inside a middleware
  const responseByI18nMiddleware = I18nMiddleware(request);

  // x-hello-from-middleware1 header is lost
  // return response;
  return responseByI18nMiddleware;
}

export const config = {
  matcher: ["/((?!api|static|.*\\..*|_next|favicon.ico|robots.txt).*)"]
};

page.ts example here:

export default async function Page() {
  const t = await getI18n();
  const allHeaders = [...headers().entries()].map(([key, value]) => `${key}: ${value}`);
  return (
    <div>
      <h1>{t("hello")}</h1>
      {allHeaders.map((header, index) => (
        <p key={index}>{header}</p>
      ))}
    </div>
  );
}

You can see there is no "x-hello-from-middleware1" header. Replace return responseByI18nMiddleware with return response then the header is shown, but i18n not works.

Expected behavior Able to set request header in middleware. It may be good if I18nMiddleware can receive argument to pass to NextResponse.next().

About (please complete the following information):

Additional context https://github.com/QuiiBz/next-international/issues/187#issuecomment-1907933546

zanzlender commented 4 months ago

Could this be solved inside createI18nMiddleware function at line 33, by adding the passed request when creating the response?

let response = NextResponse.next();

to

let response = NextResponse.next({ request: request });

I tried it out on my local repo and it copies all headers... However, I am not sure this is desired in all cases... And if it solves the problem of chaining middleware.

Another thing that I found out I could do is add new headers on top of the NextResponse returned by createI18nMiddleware. But this is only in the case if you do not care about the request headers, but only want to modify the response ones.

export function middleware(request: NextRequest) {
  const responseByI18nMiddleware = I18nMiddleware(request);

  responseByI18nMiddleware.headers.set("x-hello-from-middleware1", "hello");

  return responseByI18nMiddleware;
}

Also, maybe a little off-topic, but my use-case is that I want m custom logic to redirect the user in the middleware, but I cannot both redirect and call createI18nMiddleware, because .rewrite is already inside it.