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

fix: Ability to modify request headers #269

Merged
merged 4 commits into from
May 12, 2023

Conversation

ARochniak
Copy link
Contributor

fix #266

@vercel
Copy link

vercel bot commented May 3, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
example-next-13-next-auth ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 11, 2023 6:54pm
next-intl-docs ✅ Ready (Inspect) Visit Preview May 11, 2023 6:54pm
next-intl-example-next-13 ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 11, 2023 6:54pm
next-intl-examples-next-13 ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 11, 2023 6:54pm

@vercel
Copy link

vercel bot commented May 3, 2023

@ARochniak is attempting to deploy a commit to the Jan Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Owner

@amannn amannn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for contributing this fix @ARochniak! 👏

// Nothing yet
return undefined;
if (hasOutdatedCookie) {
request.headers.set(HEADER_LOCALE_NAME, locale);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not necessary for the current implementation in the main branch. The header is the workaround that is currently used in the RSC branch to "send" the matched locale to Server Components. It's also the reason why SSG doesn't work at this point in the RSC branch, I hope to get rid of this once ServerContext works well.

In the main branch we currently fully support SSG and don't need this workaround, therefore we shouldn't introduce it here.

Can you remove this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amannn Sorry, my bad! Sure I will remove it.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries! This is unfortunately a bit convoluted currently.

Btw. I'm currently in process of modernizing the code base a bit, the main branch now uses pnpm/turbo. Feel free to rebase to get a performance boost during development! The contributors guide is updated as well.

Next up is modernizing the test runner and build system …

@@ -1,3 +1,6 @@
// Reuse the legacy cookie name
// https://nextjs.org/docs/advanced-features/i18n-routing#leveraging-the-next_locale-cookie
export const COOKIE_LOCALE_NAME = 'NEXT_LOCALE';

// Should take precedence over the cookie
export const HEADER_LOCALE_NAME = 'X-NEXT-INTL-LOCALE';
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment above.

const responseInit = {
request: {
headers: request.headers
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the fix that allows forwarding headers, right? Can you include a regression test in middleware.test.tsx that fails for the previous implementation and now passes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure. But need some time, if it's fine for you.

Copy link
Owner

@amannn amannn May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'm really grateful for your contribution! Let me know if I can help!

@ARochniak
Copy link
Contributor Author

Hi @amannn, while I was adding test case I realised that the previous solution was not good enough as it implicitly set request headers, even in cases when it's not necessary. But the current implementation also looks not clean enough for me. I would like to hear you opinion.
P.s. Also I realised how long ago I wrote tests 😁

Copy link
Owner

@amannn amannn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the test, hope you found your way around! 🙌

Good point that working with test runners might not be that common, I've just updated the contributors guide to contain some information in regard to running and writing tests. Hopefully this helps a bit!

export default function createMiddleware(config: MiddlewareConfig) {
const configWithDefaults = receiveConfig(config);

// Currently only in use to enable a seamless upgrade path from the
// `{createIntlMiddleware} from 'next-intl/server'` API.
const matcher: Array<string> | undefined = (config as any)._matcher;

return function middleware(request: NextRequest) {
return function middleware(request: NextRequest, _?: NextFetchEvent, responseInit?: MiddlewareResponseInit) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this doesn't look quite right. You're accepting a third parameter now for the middleware, but the goal is to keep existing incoming headers, right?

I think these tests could help to verify the implementation:

it('retains request headers for the default locale', () => {
  middleware(
    createMockRequest('/', 'en', 'http://localhost:3000', undefined, {
      'x-test': 'test'
    })
  );
  expect(
    MockedNextResponse.rewrite.mock.calls[0][1].request.headers.get(
      'x-test'
    )
  ).toBe('test');
});

it('retains request headers for secondary locales', () => {
  middleware(
    createMockRequest('/de', 'de', 'http://localhost:3000', undefined, {
      'x-test': 'test'
    })
  );
  expect(
    MockedNextResponse.next.mock.calls[0][0].request.headers.get('x-test')
  ).toBe('test');
});

These tests are set up to be placed at the spot in the test suite where you've added your test!

I've added another parameter for createMockRequest here to accept extraHeaders?: Record<string, string>.

If the goal of the consumer is to modify request headers, this can be done before the middleware is invoked.

Does this help? Let me know if I can help!

Copy link
Contributor Author

@ARochniak ARochniak May 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amannn My initial intention was to provide ability to set custom request headers that can be consumed within server components. Previous implementation did it by implicitly forwarding headers from incoming request. But my concern is that it could go wrong at some point. As for me explicit setting of the responseInit looks more robust and extendable. That is how I see adjusted withExtraMiddleware example with explicit setting of the responseInit:

import createMiddleware, { NextMiddleware } from 'next-intl/middleware';
import { NextMiddlewareResult } from 'next/dist/server/web/types';
import {NextFetchEvent, NextRequest, NextResponse} from 'next/server';

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

type MiddlewareResponseInit = Parameters<typeof NextResponse.next>[0]

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, { request: { headers: request.headers } });

    // 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 hope I I was able to get my point across 😅 But if you don't see any issues with implicit forwarding headers from request I don't mind about it.

And thank you for the tests!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining @ARochniak!

Previous implementation did it by implicitly forwarding headers from incoming request. But my concern is that it could go wrong at some point.

Do you have anything particular in mind that could go wrong? I think that by adding tests we can guarantee that this continues to work in the future.

Generally, if something can be solved with the existing APIs, I wouldn't want to add features. By doing this, we have to maintain less features within the library and also for the consumer there is a single way to achieve a particular goal (if we'd support a third argument, it's questionable what happens to existing request headers for example).

Therefore I'd really be in favor of not adding more API than necessary. Hope this makes sense!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amannn Thanks for clarification. My concern is mostly based on some "God feeling" 😊. I haven't any issue about using previous approach, and I just wanted to discuss with you different option.

Copy link
Owner

@amannn amannn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks!

I've rebased your PR and fixed a lint issue. If tests pass, I'll merge this and I'll also publish a new RSC release rebased on top of this.

@ARochniak
Copy link
Contributor Author

Looks great, thanks!

I've rebased your PR and fixed a lint issue. If tests pass, I'll merge this and I'll also publish a new RSC release rebased on top of this.

Thank you, it was a pleasure to contribute and communicate with you!

@amannn
Copy link
Owner

amannn commented May 12, 2023

Thanks, the pleasure's all mine! 🙂

Here's the new RSC version (I think that's what you needed originally): 2.15.0-beta.3

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

Successfully merging this pull request may close these issues.

Modifying the request headers doesn't work
2 participants