Skip to content

Commit 747cf8e

Browse files
fix: Use correct host and protocol for alternate links when running behind a proxy (x-forwarded-host, x-forwarded-proto) (#462 by @HHongSeungWoo)
--------- Co-authored-by: Jan Amann <jan@amann.me>
1 parent 68914a3 commit 747cf8e

2 files changed

Lines changed: 53 additions & 15 deletions

File tree

packages/next-intl/src/middleware/getAlternateLinksHeaderValue.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import {NextRequest} from 'next/server';
22
import MiddlewareConfig, {
33
MiddlewareConfigWithDefaults
44
} from './NextIntlMiddlewareConfig';
5-
import {isLocaleSupportedOnDomain} from './utils';
5+
import {getHost, isLocaleSupportedOnDomain} from './utils';
66

77
function getUnprefixedUrl(config: MiddlewareConfig, request: NextRequest) {
88
const url = new URL(request.url);
9+
url.host = getHost(request.headers) ?? url.host;
10+
url.protocol = request.headers.get('x-forwarded-proto') ?? url.protocol;
11+
912
if (!url.pathname.endsWith('/')) {
1013
url.pathname += '/';
1114
}

packages/next-intl/test/middleware/getAlternateLinksHeaderValue.test.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ import {it, expect} from 'vitest';
33
import {MiddlewareConfigWithDefaults} from '../../src/middleware/NextIntlMiddlewareConfig';
44
import getAlternateLinksHeaderValue from '../../src/middleware/getAlternateLinksHeaderValue';
55

6-
function getRequest(url = 'https://example.com/') {
7-
return {url} as NextRequest;
8-
}
9-
106
it('works for prefixed routing (as-needed)', () => {
117
const config: MiddlewareConfigWithDefaults = {
128
defaultLocale: 'en',
@@ -17,7 +13,10 @@ it('works for prefixed routing (as-needed)', () => {
1713
};
1814

1915
expect(
20-
getAlternateLinksHeaderValue(config, getRequest()).split(', ')
16+
getAlternateLinksHeaderValue(
17+
config,
18+
new NextRequest('https://example.com/')
19+
).split(', ')
2120
).toEqual([
2221
'<https://example.com/>; rel="alternate"; hreflang="en"',
2322
'<https://example.com/es>; rel="alternate"; hreflang="es"',
@@ -27,7 +26,7 @@ it('works for prefixed routing (as-needed)', () => {
2726
expect(
2827
getAlternateLinksHeaderValue(
2928
config,
30-
getRequest('https://example.com/about')
29+
new NextRequest('https://example.com/about')
3130
).split(', ')
3231
).toEqual([
3332
'<https://example.com/about>; rel="alternate"; hreflang="en"',
@@ -46,7 +45,10 @@ it('works for prefixed routing (always)', () => {
4645
};
4746

4847
expect(
49-
getAlternateLinksHeaderValue(config, getRequest()).split(', ')
48+
getAlternateLinksHeaderValue(
49+
config,
50+
new NextRequest('https://example.com/')
51+
).split(', ')
5052
).toEqual([
5153
'<https://example.com/en>; rel="alternate"; hreflang="en"',
5254
'<https://example.com/es>; rel="alternate"; hreflang="es"',
@@ -56,7 +58,7 @@ it('works for prefixed routing (always)', () => {
5658
expect(
5759
getAlternateLinksHeaderValue(
5860
config,
59-
getRequest('https://example.com/about')
61+
new NextRequest('https://example.com/about')
6062
).split(', ')
6163
).toEqual([
6264
'<https://example.com/en/about>; rel="alternate"; hreflang="en"',
@@ -92,10 +94,13 @@ it("works for type domain with `localePrefix: 'as-needed'`", () => {
9294
};
9395

9496
[
95-
getAlternateLinksHeaderValue(config, getRequest()).split(', '),
9697
getAlternateLinksHeaderValue(
9798
config,
98-
getRequest('https://example.es/')
99+
new NextRequest('https://example.com/')
100+
).split(', '),
101+
getAlternateLinksHeaderValue(
102+
config,
103+
new NextRequest('https://example.es/')
99104
).split(', ')
100105
].forEach((links) => {
101106
expect(links).toEqual([
@@ -111,7 +116,7 @@ it("works for type domain with `localePrefix: 'as-needed'`", () => {
111116
expect(
112117
getAlternateLinksHeaderValue(
113118
config,
114-
getRequest('https://example.com/about')
119+
new NextRequest('https://example.com/about')
115120
).split(', ')
116121
).toEqual([
117122
'<https://example.com/about>; rel="alternate"; hreflang="en"',
@@ -150,10 +155,13 @@ it("works for type domain with `localePrefix: 'always'`", () => {
150155
};
151156

152157
[
153-
getAlternateLinksHeaderValue(config, getRequest()).split(', '),
154158
getAlternateLinksHeaderValue(
155159
config,
156-
getRequest('https://example.es/')
160+
new NextRequest('https://example.com/')
161+
).split(', '),
162+
getAlternateLinksHeaderValue(
163+
config,
164+
new NextRequest('https://example.es/')
157165
).split(', ')
158166
].forEach((links) => {
159167
expect(links).toEqual([
@@ -169,7 +177,7 @@ it("works for type domain with `localePrefix: 'always'`", () => {
169177
expect(
170178
getAlternateLinksHeaderValue(
171179
config,
172-
getRequest('https://example.com/about')
180+
new NextRequest('https://example.com/about')
173181
).split(', ')
174182
).toEqual([
175183
'<https://example.com/en/about>; rel="alternate"; hreflang="en"',
@@ -180,3 +188,30 @@ it("works for type domain with `localePrefix: 'always'`", () => {
180188
'<https://example.ca/fr/about>; rel="alternate"; hreflang="fr"'
181189
]);
182190
});
191+
192+
it('uses the external host name from headers instead of the url of the incoming request (relevant when running the app behind a proxy)', () => {
193+
const config: MiddlewareConfigWithDefaults = {
194+
defaultLocale: 'en',
195+
locales: ['en', 'es'],
196+
alternateLinks: true,
197+
localePrefix: 'as-needed',
198+
localeDetection: true
199+
};
200+
201+
expect(
202+
getAlternateLinksHeaderValue(
203+
config,
204+
new NextRequest('http://127.0.0.1/about', {
205+
headers: {
206+
host: 'example.com',
207+
'x-forwarded-host': 'example.com',
208+
'x-forwarded-proto': 'https'
209+
}
210+
})
211+
).split(', ')
212+
).toEqual([
213+
'<https://example.com/about>; rel="alternate"; hreflang="en"',
214+
'<https://example.com/es/about>; rel="alternate"; hreflang="es"',
215+
'<https://example.com/about>; rel="alternate"; hreflang="x-default"'
216+
]);
217+
});

0 commit comments

Comments
 (0)