From fa3bce649abd2c72119844ef721692141181baaf Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Sun, 20 Aug 2023 01:25:34 +0900 Subject: [PATCH 1/8] fix: host --- .../src/middleware/getAlternateLinksHeaderValue.tsx | 2 +- packages/next-intl/src/middleware/middleware.tsx | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx index bb948ce95..c630bf58c 100644 --- a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx +++ b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx @@ -5,7 +5,7 @@ import MiddlewareConfig, { import {isLocaleSupportedOnDomain} from './utils'; function getUnprefixedUrl(config: MiddlewareConfig, request: NextRequest) { - const url = new URL(request.url); + const url = request.nextUrl.clone(); if (!url.pathname.endsWith('/')) { url.pathname += '/'; } diff --git a/packages/next-intl/src/middleware/middleware.tsx b/packages/next-intl/src/middleware/middleware.tsx index c4663156e..93191e967 100644 --- a/packages/next-intl/src/middleware/middleware.tsx +++ b/packages/next-intl/src/middleware/middleware.tsx @@ -7,6 +7,7 @@ import getAlternateLinksHeaderValue from './getAlternateLinksHeaderValue'; import resolveLocale from './resolveLocale'; import { getBestMatchingDomain, + getHost, getLocaleFromPathname, isLocaleSupportedOnDomain } from './utils'; @@ -32,6 +33,9 @@ export default function createMiddleware(config: MiddlewareConfig) { const matcher: Array | undefined = (config as any)._matcher; return function middleware(request: NextRequest) { + // if Next.js behind a reverse proxy, it cannot get the correct host. ex) localhost, 172.x.x.x + // We need to set the host here, because Next.js doesn't do it for us + request.nextUrl.host = getHost(request.headers) ?? request.nextUrl.host; const matches = !matcher || matcher.some((pattern) => request.nextUrl.pathname.match(pattern)); @@ -68,7 +72,10 @@ export default function createMiddleware(config: MiddlewareConfig) { } function rewrite(url: string) { - return NextResponse.rewrite(new URL(url, request.url), getResponseInit()); + return NextResponse.rewrite( + new URL(url, request.nextUrl.href), + getResponseInit() + ); } function next() { @@ -76,7 +83,7 @@ export default function createMiddleware(config: MiddlewareConfig) { } function redirect(url: string, host?: string) { - const urlObj = new URL(url, request.url); + const urlObj = new URL(url, request.nextUrl.href); if (domainConfigs.length > 0) { if (!host) { From e0df672113bd40c85db63ed7a2af03043df4453a Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Mon, 21 Aug 2023 17:39:44 +0900 Subject: [PATCH 2/8] revert: middleware --- packages/next-intl/src/middleware/middleware.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/next-intl/src/middleware/middleware.tsx b/packages/next-intl/src/middleware/middleware.tsx index 93191e967..244708995 100644 --- a/packages/next-intl/src/middleware/middleware.tsx +++ b/packages/next-intl/src/middleware/middleware.tsx @@ -7,7 +7,6 @@ import getAlternateLinksHeaderValue from './getAlternateLinksHeaderValue'; import resolveLocale from './resolveLocale'; import { getBestMatchingDomain, - getHost, getLocaleFromPathname, isLocaleSupportedOnDomain } from './utils'; @@ -33,9 +32,6 @@ export default function createMiddleware(config: MiddlewareConfig) { const matcher: Array | undefined = (config as any)._matcher; return function middleware(request: NextRequest) { - // if Next.js behind a reverse proxy, it cannot get the correct host. ex) localhost, 172.x.x.x - // We need to set the host here, because Next.js doesn't do it for us - request.nextUrl.host = getHost(request.headers) ?? request.nextUrl.host; const matches = !matcher || matcher.some((pattern) => request.nextUrl.pathname.match(pattern)); From 2ab2d4874eb03bf204ae2b89a4082ad6303179de Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Mon, 21 Aug 2023 17:48:09 +0900 Subject: [PATCH 3/8] fix: getUnprefixedUrl --- .../src/middleware/getAlternateLinksHeaderValue.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx index c630bf58c..1e5618ac9 100644 --- a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx +++ b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx @@ -2,10 +2,11 @@ import {NextRequest} from 'next/server'; import MiddlewareConfig, { MiddlewareConfigWithDefaults } from './NextIntlMiddlewareConfig'; -import {isLocaleSupportedOnDomain} from './utils'; +import {getHost, isLocaleSupportedOnDomain} from './utils'; function getUnprefixedUrl(config: MiddlewareConfig, request: NextRequest) { - const url = request.nextUrl.clone(); + const url = new URL(request.url); + url.host = getHost(request.headers) ?? url.host; if (!url.pathname.endsWith('/')) { url.pathname += '/'; } From 441223af43d25a7faee83d50838bd16702eed36d Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Mon, 21 Aug 2023 17:48:55 +0900 Subject: [PATCH 4/8] test: fix test mock --- .../test/middleware/getAlternateLinksHeaderValue.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx index cb3fc3406..afd92f4f7 100644 --- a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx +++ b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx @@ -1,10 +1,10 @@ import {NextRequest} from 'next/server'; -import {it, expect} from 'vitest'; +import {expect, it} from 'vitest'; import {MiddlewareConfigWithDefaults} from '../../src/middleware/NextIntlMiddlewareConfig'; import getAlternateLinksHeaderValue from '../../src/middleware/getAlternateLinksHeaderValue'; function getRequest(url = 'https://example.com/') { - return {url} as NextRequest; + return new NextRequest(url); } it('works for prefixed routing (as-needed)', () => { From 638628a9add6d67bd46b2d958a0c66325c86f2ef Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Mon, 21 Aug 2023 17:56:53 +0900 Subject: [PATCH 5/8] revert: middleware.tsx --- packages/next-intl/src/middleware/middleware.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/next-intl/src/middleware/middleware.tsx b/packages/next-intl/src/middleware/middleware.tsx index 244708995..c4663156e 100644 --- a/packages/next-intl/src/middleware/middleware.tsx +++ b/packages/next-intl/src/middleware/middleware.tsx @@ -68,10 +68,7 @@ export default function createMiddleware(config: MiddlewareConfig) { } function rewrite(url: string) { - return NextResponse.rewrite( - new URL(url, request.nextUrl.href), - getResponseInit() - ); + return NextResponse.rewrite(new URL(url, request.url), getResponseInit()); } function next() { @@ -79,7 +76,7 @@ export default function createMiddleware(config: MiddlewareConfig) { } function redirect(url: string, host?: string) { - const urlObj = new URL(url, request.nextUrl.href); + const urlObj = new URL(url, request.url); if (domainConfigs.length > 0) { if (!host) { From 97653c06e8abc69d2eaf44d2016c21d19dade44c Mon Sep 17 00:00:00 2001 From: Seungwoo Hong Date: Mon, 21 Aug 2023 21:23:00 +0900 Subject: [PATCH 6/8] test: . --- .../test/middleware/getAlternateLinksHeaderValue.test.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx index afd92f4f7..0f362d4ba 100644 --- a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx +++ b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx @@ -3,8 +3,10 @@ import {expect, it} from 'vitest'; import {MiddlewareConfigWithDefaults} from '../../src/middleware/NextIntlMiddlewareConfig'; import getAlternateLinksHeaderValue from '../../src/middleware/getAlternateLinksHeaderValue'; -function getRequest(url = 'https://example.com/') { - return new NextRequest(url); +function getRequest(url = 'https://internal-url.com/') { + return new NextRequest(url, { + headers: {host: 'example.com', 'x-forwarded-host': 'example.com'} + }); } it('works for prefixed routing (as-needed)', () => { From 6d21dec5e809c84996def6ee2dd7ba6bb94bead8 Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Tue, 22 Aug 2023 11:49:51 +0200 Subject: [PATCH 7/8] Use forwarded protocol as well, add regression test, replace `getRequest` with `NextRequest` constructor to avoid unnecessary indirection --- .../getAlternateLinksHeaderValue.tsx | 2 + .../getAlternateLinksHeaderValue.test.tsx | 67 ++++++++++++++----- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx index 1e5618ac9..325fb0d25 100644 --- a/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx +++ b/packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx @@ -7,6 +7,8 @@ import {getHost, isLocaleSupportedOnDomain} from './utils'; function getUnprefixedUrl(config: MiddlewareConfig, request: NextRequest) { const url = new URL(request.url); url.host = getHost(request.headers) ?? url.host; + url.protocol = request.headers.get('x-forwarded-proto') ?? url.protocol; + if (!url.pathname.endsWith('/')) { url.pathname += '/'; } diff --git a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx index 0f362d4ba..feef599a7 100644 --- a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx +++ b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx @@ -1,14 +1,8 @@ import {NextRequest} from 'next/server'; -import {expect, it} from 'vitest'; +import {it, expect} from 'vitest'; import {MiddlewareConfigWithDefaults} from '../../src/middleware/NextIntlMiddlewareConfig'; import getAlternateLinksHeaderValue from '../../src/middleware/getAlternateLinksHeaderValue'; -function getRequest(url = 'https://internal-url.com/') { - return new NextRequest(url, { - headers: {host: 'example.com', 'x-forwarded-host': 'example.com'} - }); -} - it('works for prefixed routing (as-needed)', () => { const config: MiddlewareConfigWithDefaults = { defaultLocale: 'en', @@ -19,7 +13,10 @@ it('works for prefixed routing (as-needed)', () => { }; expect( - getAlternateLinksHeaderValue(config, getRequest()).split(', ') + getAlternateLinksHeaderValue( + config, + new NextRequest('https://example.com/') + ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="es"', @@ -29,7 +26,7 @@ it('works for prefixed routing (as-needed)', () => { expect( getAlternateLinksHeaderValue( config, - getRequest('https://example.com/about') + new NextRequest('https://example.com/about') ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', @@ -48,7 +45,10 @@ it('works for prefixed routing (always)', () => { }; expect( - getAlternateLinksHeaderValue(config, getRequest()).split(', ') + getAlternateLinksHeaderValue( + config, + new NextRequest('https://example.com/') + ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', '; rel="alternate"; hreflang="es"', @@ -58,7 +58,7 @@ it('works for prefixed routing (always)', () => { expect( getAlternateLinksHeaderValue( config, - getRequest('https://example.com/about') + new NextRequest('https://example.com/about') ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', @@ -94,10 +94,13 @@ it("works for type domain with `localePrefix: 'as-needed'`", () => { }; [ - getAlternateLinksHeaderValue(config, getRequest()).split(', '), getAlternateLinksHeaderValue( config, - getRequest('https://example.es/') + new NextRequest('https://example.com/') + ).split(', '), + getAlternateLinksHeaderValue( + config, + new NextRequest('https://example.es/') ).split(', ') ].forEach((links) => { expect(links).toEqual([ @@ -113,7 +116,7 @@ it("works for type domain with `localePrefix: 'as-needed'`", () => { expect( getAlternateLinksHeaderValue( config, - getRequest('https://example.com/about') + new NextRequest('https://example.com/about') ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', @@ -152,10 +155,13 @@ it("works for type domain with `localePrefix: 'always'`", () => { }; [ - getAlternateLinksHeaderValue(config, getRequest()).split(', '), getAlternateLinksHeaderValue( config, - getRequest('https://example.es/') + new NextRequest('https://example.com/') + ).split(', '), + getAlternateLinksHeaderValue( + config, + new NextRequest('https://example.es/') ).split(', ') ].forEach((links) => { expect(links).toEqual([ @@ -171,7 +177,7 @@ it("works for type domain with `localePrefix: 'always'`", () => { expect( getAlternateLinksHeaderValue( config, - getRequest('https://example.com/about') + new NextRequest('https://example.com/about') ).split(', ') ).toEqual([ '; rel="alternate"; hreflang="en"', @@ -182,3 +188,30 @@ it("works for type domain with `localePrefix: 'always'`", () => { '; rel="alternate"; hreflang="fr"' ]); }); + +it('use the external host name from headers instead of the url of the incoming request (relevant when running the app behind a proxy)', () => { + const config: MiddlewareConfigWithDefaults = { + defaultLocale: 'en', + locales: ['en', 'es'], + alternateLinks: true, + localePrefix: 'as-needed', + localeDetection: true + }; + + expect( + getAlternateLinksHeaderValue( + config, + new NextRequest('http://127.0.0.1/about', { + headers: { + host: 'example.com', + 'x-forwarded-host': 'example.com', + 'x-forwarded-proto': 'https' + } + }) + ).split(', ') + ).toEqual([ + '; rel="alternate"; hreflang="en"', + '; rel="alternate"; hreflang="es"', + '; rel="alternate"; hreflang="x-default"' + ]); +}); From 763ab83f923eed1e91ae0d2ea0223558767e69cb Mon Sep 17 00:00:00 2001 From: Jan Amann Date: Tue, 22 Aug 2023 11:52:44 +0200 Subject: [PATCH 8/8] Fix typo --- .../test/middleware/getAlternateLinksHeaderValue.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx index feef599a7..c51956d77 100644 --- a/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx +++ b/packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx @@ -189,7 +189,7 @@ it("works for type domain with `localePrefix: 'always'`", () => { ]); }); -it('use the external host name from headers instead of the url of the incoming request (relevant when running the app behind a proxy)', () => { +it('uses the external host name from headers instead of the url of the incoming request (relevant when running the app behind a proxy)', () => { const config: MiddlewareConfigWithDefaults = { defaultLocale: 'en', locales: ['en', 'es'],