amannn / next-intl

🌐 Internationalization (i18n) for Next.js
https://next-intl-docs.vercel.app
MIT License
2.29k stars 211 forks source link

Documentation for composing middlewares #247

Closed amannn closed 1 year ago

amannn commented 1 year ago

Is your feature request related to a problem? Please describe.

There are other middlewares like Auth.js and it would be good to provide documentation on how to use the middleware from next-intl in combination with those.

Describe the solution you'd like

A section in the middleware documentation.

Previous discussion:

/cc @narakhan @kieranm @velychkodmytro20

ghost commented 1 year ago

@amannn don't have time to handle this right now, will open a PR within the next couple of days. Assuming you want it targeting the main branch?

amannn commented 1 year ago

So if you're going to chain middleware it needs to go last, and using the API they expose is probably going to result in the least severe concussion from banging your head against the table.

@narakhan Hope you're recovering well currently! 😄

Thank you so much for chiming in here, if you're interested to look into this I'd be more than happy! 🙌

I'm currently thinking about a section in the middleware docs where a) we explain conceptually what it means to compose middleware (maybe similar to https://github.com/amannn/next-intl/pull/149#issuecomment-1471592626) and then b) a guide on how to integrate with Auth.js, since it's the most popular middleware for Next.js as far as I know.

Optionally, we could also add and link to an example in the repository (could be based on example-next-13). This could be used for simple e2e tests too if that helps, also to stay in sync with future releases of Auth.js.

Assuming you want it targeting the main branch?

Yep, I've recently cleaned up the RSC branch to only contain code specifically for RSC. Other improvements like the middleware are now available in stable releases and the documentation is also deployed on main.

amannn commented 1 year ago

I just had a moment to experiment with composing middlewares. This code seems to be working:

import createMiddleware from 'next-intl/middleware';
import {NextFetchEvent, NextMiddleware, NextRequest} from 'next/server';

export default withExtraMiddleware(
  createMiddleware({
    locales: ['en', 'de'],
    defaultLocale: 'en'
  })
);

function withExtraMiddleware(next: NextMiddleware) {
  return async (request: NextRequest, event: NextFetchEvent) => {
    // Step 1: Potentially change the incoming request
    request.headers.set('x-test', 'test');

    // Step 2: Call the nested next-intl middleware
    const response = next(request, event);

    // Step 3: Potentially change the response
    if (response) {
      response.headers.set('x-test', 'test');
    }

    return response;
  };
}

export const config = {
  // Skip all paths that should not be internationalized
  matcher: ['/((?!_next|.*\\..*).*)']
};

I also found there is a (slightly hacky) way to programmatically compose rewrites on top of next-intl that could be interesting to implement named routes with localized pathnames:

    // ...

    // Step 2: Call the nested next-intl middleware
    const response = next(request, event);

    // Step 3: Potentially change the response
    if (response) {
      // Add a programmatic rewrite
      // https://github.com/vercel/next.js/discussions/33477
      if (request.nextUrl.pathname === '/de/über') {
        const url = new URL('/de/about', request.url);
        response.headers.set('x-middleware-rewrite', url.toString());
      }
    }

    // ...

@narakhan Are you still interested on working on the Auth.js example? I could take care of the docs.

ghost commented 1 year ago

I just had a moment to experiment with composing middlewares. This code seems to be working:

import createMiddleware from 'next-intl/middleware';
import {NextFetchEvent, NextMiddleware, NextRequest} from 'next/server';

export default withExtraMiddleware(
  createMiddleware({
    locales: ['en', 'de'],
    defaultLocale: 'en'
  })
);

function withExtraMiddleware(next: NextMiddleware) {
  return async (request: NextRequest, event: NextFetchEvent) => {
    // Step 1: Potentially change the incoming request
    request.headers.set('x-test', 'test');

    // Step 2: Call the nested next-intl middleware
    const response = await next(request, event);

    // Step 3: Potentially change the response
    if (response) {
      response.headers.set('x-test', 'test');
    }

    return response;
  };
}

export const config = {
  // Skip all paths that should not be internationalized
  matcher: ['/((?!_next|.*\\..*).*)']
};

I also found there is a (slightly hacky) way to programmatically compose rewrites on top of next-intl that could be interesting to implement named routes with localized pathnames:

    // ...

    // Step 2: Call the nested next-intl middleware
    const response = await next(request, event);

    // Step 3: Potentially change the response
    if (response) {
      // Add a programmatic rewrite
      // https://github.com/vercel/next.js/discussions/33477
      if (request.nextUrl.pathname === '/de/über') {
        const url = new URL('/de/about', request.url);
        response.headers.set('x-middleware-rewrite', url.toString());
      }
    }

    // ...

@narakhan Are you still interested on working on the Auth.js example? I could take care of the docs.

Sorry likely don't have time to handle it right now.

ryanburns23 commented 1 year ago

I have the need to do something similar but my config for createIntlMiddleware is dynamically fetched from the edge. I ended up with something like this but I am not convinced it is the right approach.

async function middleware(req: NextRequest): Promise<NextResponse | void> {
  // Fetch config from edge
  const config = await getConfigFromHost(req.nextUrl.host);

  // Set custom header
  const response = NextResponse.next();
  if (config.id) {
    response.headers.set('x-marketplace-id', config.id || 'unknown');
  }

  const intlMiddleware = createIntlMiddleware(config.intl);
  const intlResponse = await intlMiddleware(req);
  // Merge the headers from the response with the intlResponse headers
  for (const [key, value] of response.headers.entries()) {
    intlResponse.headers.set(key, value);
  }
  return intlResponse;
}

export default middleware

It is working with one es-lint error of Unexpected await of a non-Promise (non-"Thenable"). This could be a useful case to put in the doc's potentially if there is a best practice approach.

amannn commented 1 year ago

Thanks everyone for participating and especially @narakhan for sharing his middleware implementation for Auth.js! There are now new docs on composing middlewares, including an example for Auth.js.

@ryanburns23 The new docs should hopefully help with your use case, let me know if there are open questions!

bilel-lm commented 1 year ago

Thanks everyone for participating and especially @narakhan for sharing his middleware implementation for Auth.js! There are now new docs on composing middlewares, including an example for Auth.js.

@ryanburns23 The new docs should hopefully help with your use case, let me know if there are open questions!

Thanks for the release, while browsing the new middleware documentation I noticed that the provided example repos doesn't exist (example repos)

EDIT: this is the example repos URL (it was redirecting to wrong branch) https://github.com/amannn/next-intl/tree/feat/next-13-rsc/packages/example-next-13-next-auth

amannn commented 1 year ago

@bilel-lm Oh, good point—thanks! I've moved some files today, the links should be fixed now.

guifeliper commented 1 year ago

Thanks everyone for participating and especially @narakhan for sharing his middleware implementation for Auth.js! There are now new docs on composing middlewares, including an example for Auth.js.

@ryanburns23 The new docs should hopefully help with your use case, let me know if there are open questions!

Hello @amannn I have been implementing some similar solutions, and I notice that the example does not redirect to "/secret" page after login. How would you suggest redirecting the user to "/secret" once logged in? I am having some issues with this implementation.

Thank you so much!

amannn commented 1 year ago

@guifeliper That's a good point, thanks for noticing!

I'm not really experienced with Auth.js, this PR was my only exposure so far to the library. It is sometimes a bit tricky to find the right config.

If you're interested in contributing, we've got a minimalistic example in this repo along with some tests:

That could be helpful to quickly iterate with different config and figure out a solution, if you like!

Sashkan commented 10 months ago

I'm so sorry to re-open this issue, but I'm having trouble understanding the underlying logic for middleware composition. I'm handling my cookie-based authentication through a custom middleware which looks like this:

import { NextRequest, NextResponse } from "next/server";

import { HOME_PATH, LOGIN_PATH, TERMS_PATH } from "constants/paths";

const accessTokenCookieName =
  process.env.ACCESS_TOKEN_COOKIE_NAME ?? "hal.access-token";
const PUBLIC_PATHS = [LOGIN_PATH, TERMS_PATH];

/**
 * Redirect to login page if no access token is found
 */
export function middleware(request: NextRequest) {
  const token = request.cookies.get(accessTokenCookieName);

  if (token) {
    // redirect to homepage if token is found on login page
    if (request.nextUrl.pathname === LOGIN_PATH) {
      return NextResponse.redirect(new URL(HOME_PATH, request.nextUrl.origin));
    }

    return NextResponse.next();
  }

  // Skip middleware for the public pages
  if (PUBLIC_PATHS.includes(request.nextUrl.pathname)) {
    return NextResponse.next();
  }

  return NextResponse.redirect(new URL(LOGIN_PATH, request.nextUrl.origin));
}

export const config = {
  /**
   * Match all request paths except:
   * - API
   * - NextJS chunks
   * - favicon.ico
   */
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

I tried adapting it using the piece of code provided earlier in this issue, but I'm fairly new to next middleware usage, and I want to make sure I understand how it affects the response, and how I can combine both my authentication logic with the middleware provided by next-intl

kimmo-koo commented 10 months ago

Anyone have an example of integrating Supabase SSR cookie refreshing middleware with next-intl middleware when using Next.js 14?

AssisrMatheus commented 9 months ago

Anyone have an example of integrating Supabase SSR cookie refreshing middleware with next-intl middleware when using Next.js 14?

Did you find a solution?

mkbctrl commented 8 months ago

@AssisrMatheus @kimmo-koo maybe this will help you: https://github.com/amannn/next-intl/issues/719

PhongLi commented 7 months ago

I'm so sorry to re-open this issue, but I'm having trouble understanding the underlying logic for middleware composition. I'm handling my cookie-based authentication through a custom middleware which looks like this:

import { NextRequest, NextResponse } from "next/server";

import { HOME_PATH, LOGIN_PATH, TERMS_PATH } from "constants/paths";

const accessTokenCookieName =
  process.env.ACCESS_TOKEN_COOKIE_NAME ?? "hal.access-token";
const PUBLIC_PATHS = [LOGIN_PATH, TERMS_PATH];

/**
 * Redirect to login page if no access token is found
 */
export function middleware(request: NextRequest) {
  const token = request.cookies.get(accessTokenCookieName);

  if (token) {
    // redirect to homepage if token is found on login page
    if (request.nextUrl.pathname === LOGIN_PATH) {
      return NextResponse.redirect(new URL(HOME_PATH, request.nextUrl.origin));
    }

    return NextResponse.next();
  }

  // Skip middleware for the public pages
  if (PUBLIC_PATHS.includes(request.nextUrl.pathname)) {
    return NextResponse.next();
  }

  return NextResponse.redirect(new URL(LOGIN_PATH, request.nextUrl.origin));
}

export const config = {
  /**
   * Match all request paths except:
   * - API
   * - NextJS chunks
   * - favicon.ico
   */
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};

I tried adapting it using the piece of code provided earlier in this issue, but I'm fairly new to next middleware usage, and I want to make sure I understand how it affects the response, and how I can combine both my authentication logic with the middleware provided by next-intl

any updates ?

MickaelMuller commented 7 months ago

+1 @PhongLi I need updates too