Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation for composing middlewares #247

Closed
amannn opened this issue Apr 17, 2023 · 16 comments · Fixed by #261
Closed

Documentation for composing middlewares #247

amannn opened this issue Apr 17, 2023 · 16 comments · Fixed by #261
Labels
documentation Improvements or additions to documentation

Comments

@amannn
Copy link
Owner

amannn commented Apr 17, 2023

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

@amannn amannn added the documentation Improvements or additions to documentation label Apr 17, 2023
@ghost
Copy link

ghost commented Apr 17, 2023

@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
Copy link
Owner Author

amannn commented Apr 17, 2023

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 #149 (comment)) 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
Copy link
Owner Author

amannn commented Apr 26, 2023

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
Copy link

ghost commented Apr 26, 2023

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
Copy link

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 added a commit that referenced this issue May 2, 2023
…). Many thanks to @narakhan for sharing his middleware implementation! [skip ci]

Fixes #247
@amannn
Copy link
Owner Author

amannn commented May 2, 2023

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
Copy link

bilel-lm commented May 4, 2023

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
Copy link
Owner Author

amannn commented May 4, 2023

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

@guifeliper
Copy link

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
Copy link
Owner Author

amannn commented Jul 24, 2023

@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
Copy link

Sashkan commented Oct 19, 2023

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
Copy link

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

@AssisrMatheus
Copy link

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
Copy link

mkbctrl commented Jan 6, 2024

@AssisrMatheus @kimmo-koo maybe this will help you:
#719

@PhongLi
Copy link

PhongLi commented Jan 19, 2024

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
Copy link

MickaelMuller commented Jan 24, 2024

+1 @PhongLi
I need updates too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants