Skip to content

Commit 9a5a152

Browse files
ijjkkodiakhq[bot]
andauthored
Update redirect handling for locale domains (#17856)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 9300151 commit 9a5a152

File tree

8 files changed

+191
-143
lines changed

8 files changed

+191
-143
lines changed

packages/next/build/webpack/loaders/next-serverless-loader.ts

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -222,64 +222,95 @@ const nextServerlessLoader: loader.Loader = function () {
222222
const i18n = ${i18n}
223223
const accept = require('@hapi/accept')
224224
const { detectLocaleCookie } = require('next/dist/next-server/lib/i18n/detect-locale-cookie')
225-
const { detectDomainLocales } = require('next/dist/next-server/lib/i18n/detect-domain-locales')
225+
const { detectDomainLocale } = require('next/dist/next-server/lib/i18n/detect-domain-locale')
226226
const { normalizeLocalePath } = require('next/dist/next-server/lib/i18n/normalize-locale-path')
227+
let locales = i18n.locales
228+
let defaultLocale = i18n.defaultLocale
227229
let detectedLocale = detectLocaleCookie(req, i18n.locales)
228230
229-
const { defaultLocale, locales } = detectDomainLocales(
230-
req,
231+
const detectedDomain = detectDomainLocale(
231232
i18n.domains,
232-
i18n.locales,
233-
i18n.defaultLocale,
233+
req,
234234
)
235+
if (detectedDomain) {
236+
defaultLocale = detectedDomain.defaultLocale
237+
detectedLocale = defaultLocale
238+
}
235239
236240
if (!detectedLocale) {
237241
detectedLocale = accept.language(
238242
req.headers['accept-language'],
239-
locales
243+
i18n.locales
240244
)
241245
}
242246
247+
let localeDomainRedirect
248+
const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales)
249+
250+
if (localePathResult.detectedLocale) {
251+
detectedLocale = localePathResult.detectedLocale
252+
req.url = formatUrl({
253+
...parsedUrl,
254+
pathname: localePathResult.pathname,
255+
})
256+
parsedUrl.pathname = localePathResult.pathname
257+
258+
// check if the locale prefix matches a domain's defaultLocale
259+
// and we're on a locale specific domain if so redirect to that domain
260+
if (detectedDomain) {
261+
const matchedDomain = detectDomainLocale(
262+
i18n.domains,
263+
undefined,
264+
detectedLocale
265+
)
266+
267+
if (matchedDomain) {
268+
localeDomainRedirect = \`http\${
269+
matchedDomain.http ? '' : 's'
270+
}://\${matchedDomain.domain}\`
271+
}
272+
}
273+
}
274+
243275
const denormalizedPagePath = denormalizePagePath(parsedUrl.pathname || '/')
244-
const detectedDefaultLocale = !detectedLocale || detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
276+
const detectedDefaultLocale =
277+
!detectedLocale ||
278+
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
245279
const shouldStripDefaultLocale =
246280
detectedDefaultLocale &&
247-
denormalizedPagePath.toLowerCase() === \`/\${defaultLocale.toLowerCase()}\`
281+
denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\`
248282
const shouldAddLocalePrefix =
249283
!detectedDefaultLocale && denormalizedPagePath === '/'
250284
251-
detectedLocale = detectedLocale || defaultLocale
285+
detectedLocale = detectedLocale || i18n.defaultLocale
252286
253287
if (
254288
!fromExport &&
255289
!nextStartMode &&
256290
i18n.localeDetection !== false &&
257-
(shouldAddLocalePrefix || shouldStripDefaultLocale)
291+
(
292+
localeDomainRedirect ||
293+
shouldAddLocalePrefix ||
294+
shouldStripDefaultLocale
295+
)
258296
) {
259297
res.setHeader(
260298
'Location',
261299
formatUrl({
262300
// make sure to include any query values when redirecting
263301
...parsedUrl,
264-
pathname: shouldStripDefaultLocale ? '/' : \`/\${detectedLocale}\`,
302+
pathname:
303+
localeDomainRedirect
304+
? localeDomainRedirect
305+
: shouldStripDefaultLocale
306+
? '/'
307+
: \`/\${detectedLocale}\`,
265308
})
266309
)
267310
res.statusCode = 307
268311
res.end()
269312
return
270313
}
271-
272-
const localePathResult = normalizeLocalePath(parsedUrl.pathname, locales)
273-
274-
if (localePathResult.detectedLocale) {
275-
detectedLocale = localePathResult.detectedLocale
276-
req.url = formatUrl({
277-
...parsedUrl,
278-
pathname: localePathResult.pathname,
279-
})
280-
parsedUrl.pathname = localePathResult.pathname
281-
}
282-
283314
detectedLocale = detectedLocale || defaultLocale
284315
`
285316
: `
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { IncomingMessage } from 'http'
2+
3+
export function detectDomainLocale(
4+
domainItems:
5+
| Array<{
6+
http?: boolean
7+
domain: string
8+
defaultLocale: string
9+
}>
10+
| undefined,
11+
req?: IncomingMessage,
12+
detectedLocale?: string
13+
) {
14+
let domainItem:
15+
| {
16+
http?: boolean
17+
domain: string
18+
defaultLocale: string
19+
}
20+
| undefined
21+
22+
if (domainItems) {
23+
const { host } = req?.headers || {}
24+
// remove port from host and remove port if present
25+
const hostname = host?.split(':')[0].toLowerCase()
26+
27+
for (const item of domainItems) {
28+
// remove port if present
29+
const domainHostname = item.domain?.split(':')[0].toLowerCase()
30+
if (
31+
hostname === domainHostname ||
32+
detectedLocale?.toLowerCase() === item.defaultLocale.toLowerCase()
33+
) {
34+
domainItem = item
35+
break
36+
}
37+
}
38+
}
39+
40+
return domainItem
41+
}

packages/next/next-server/lib/i18n/detect-domain-locales.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

packages/next/next-server/server/config.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -238,22 +238,7 @@ function assignDefaults(userConfig: { [key: string]: any }) {
238238
if (!item || typeof item !== 'object') return true
239239
if (!item.defaultLocale) return true
240240
if (!item.domain || typeof item.domain !== 'string') return true
241-
if (!item.locales || !Array.isArray(item.locales)) return true
242241

243-
const invalidLocales = item.locales.filter(
244-
(locale: string) => !i18n.locales.includes(locale)
245-
)
246-
247-
if (invalidLocales.length > 0) {
248-
console.error(
249-
`i18n.domains item "${
250-
item.domain
251-
}" has the following locales (${invalidLocales.join(
252-
', '
253-
)}) that aren't provided in the main i18n.locales. Add them to the i18n.locales list or remove them from the domains item locales to continue.\n`
254-
)
255-
return true
256-
}
257242
return false
258243
})
259244

packages/next/next-server/server/next-server.ts

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ import accept from '@hapi/accept'
7979
import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
8080
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
8181
import * as Log from '../../build/output/log'
82-
import { detectDomainLocales } from '../lib/i18n/detect-domain-locales'
82+
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'
8383

8484
const getCustomRouteMatcher = pathMatch(true)
8585

@@ -306,67 +306,85 @@ export default class Server {
306306
if (i18n && !parsedUrl.pathname?.startsWith('/_next')) {
307307
// get pathname from URL with basePath stripped for locale detection
308308
const { pathname, ...parsed } = parseUrl(req.url || '/')
309+
let defaultLocale = i18n.defaultLocale
309310
let detectedLocale = detectLocaleCookie(req, i18n.locales)
310311

311-
const { defaultLocale, locales } = detectDomainLocales(
312-
req,
313-
i18n.domains,
314-
i18n.locales,
315-
i18n.defaultLocale
316-
)
312+
const detectedDomain = detectDomainLocale(i18n.domains, req)
313+
if (detectedDomain) {
314+
defaultLocale = detectedDomain.defaultLocale
315+
detectedLocale = defaultLocale
316+
}
317317

318318
if (!detectedLocale) {
319319
detectedLocale = accept.language(
320320
req.headers['accept-language'],
321-
locales
321+
i18n.locales
322322
)
323323
}
324324

325+
let localeDomainRedirect: string | undefined
326+
const localePathResult = normalizeLocalePath(pathname!, i18n.locales)
327+
328+
if (localePathResult.detectedLocale) {
329+
detectedLocale = localePathResult.detectedLocale
330+
req.url = formatUrl({
331+
...parsed,
332+
pathname: localePathResult.pathname,
333+
})
334+
parsedUrl.pathname = localePathResult.pathname
335+
336+
// check if the locale prefix matches a domain's defaultLocale
337+
// and we're on a locale specific domain if so redirect to that domain
338+
if (detectedDomain) {
339+
const matchedDomain = detectDomainLocale(
340+
i18n.domains,
341+
undefined,
342+
detectedLocale
343+
)
344+
345+
if (matchedDomain) {
346+
localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${
347+
matchedDomain?.domain
348+
}`
349+
}
350+
}
351+
}
352+
325353
const denormalizedPagePath = denormalizePagePath(pathname || '/')
326354
const detectedDefaultLocale =
327355
!detectedLocale ||
328356
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
329357
const shouldStripDefaultLocale =
330358
detectedDefaultLocale &&
331-
denormalizedPagePath.toLowerCase() === `/${defaultLocale.toLowerCase()}`
359+
denormalizedPagePath.toLowerCase() ===
360+
`/${i18n.defaultLocale.toLowerCase()}`
332361
const shouldAddLocalePrefix =
333362
!detectedDefaultLocale && denormalizedPagePath === '/'
334363

335-
detectedLocale = detectedLocale || defaultLocale
364+
detectedLocale = detectedLocale || i18n.defaultLocale
336365

337366
if (
338367
i18n.localeDetection !== false &&
339-
(shouldAddLocalePrefix || shouldStripDefaultLocale)
368+
(localeDomainRedirect ||
369+
shouldAddLocalePrefix ||
370+
shouldStripDefaultLocale)
340371
) {
341372
res.setHeader(
342373
'Location',
343374
formatUrl({
344375
// make sure to include any query values when redirecting
345376
...parsed,
346-
pathname: shouldStripDefaultLocale ? '/' : `/${detectedLocale}`,
377+
pathname: localeDomainRedirect
378+
? localeDomainRedirect
379+
: shouldStripDefaultLocale
380+
? '/'
381+
: `/${detectedLocale}`,
347382
})
348383
)
349384
res.statusCode = 307
350385
res.end()
351386
return
352387
}
353-
354-
const localePathResult = normalizeLocalePath(pathname!, locales)
355-
356-
if (localePathResult.detectedLocale) {
357-
detectedLocale = localePathResult.detectedLocale
358-
req.url = formatUrl({
359-
...parsed,
360-
pathname: localePathResult.pathname,
361-
})
362-
parsedUrl.pathname = localePathResult.pathname
363-
}
364-
365-
// TODO: render with domain specific locales and defaultLocale also?
366-
// Currently locale specific domains will have all locales populated
367-
// under router.locales instead of only the domain specific ones
368-
parsedUrl.query.__nextLocales = i18n.locales
369-
// parsedUrl.query.__nextDefaultLocale = defaultLocale
370388
parsedUrl.query.__nextLocale = detectedLocale || defaultLocale
371389
}
372390

@@ -517,21 +535,15 @@ export default class Server {
517535

518536
if (i18n) {
519537
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
520-
const { defaultLocale } = detectDomainLocales(
521-
req,
522-
i18n.domains,
523-
i18n.locales,
524-
i18n.defaultLocale
525-
)
538+
const { defaultLocale } =
539+
detectDomainLocale(i18n.domains, req) || {}
526540
let detectedLocale = defaultLocale
527541

528542
if (localePathResult.detectedLocale) {
529543
pathname = localePathResult.pathname
530544
detectedLocale = localePathResult.detectedLocale
531545
}
532-
_parsedUrl.query.__nextLocales = i18n.locales
533-
_parsedUrl.query.__nextLocale = detectedLocale
534-
// _parsedUrl.query.__nextDefaultLocale = defaultLocale
546+
_parsedUrl.query.__nextLocale = detectedLocale!
535547
}
536548
pathname = getRouteFromAssetPath(pathname, '.json')
537549

@@ -1078,8 +1090,6 @@ export default class Server {
10781090
amp: query.amp,
10791091
_nextDataReq: query._nextDataReq,
10801092
__nextLocale: query.__nextLocale,
1081-
__nextLocales: query.__nextLocales,
1082-
// __nextDefaultLocale: query.__nextDefaultLocale,
10831093
}
10841094
: query),
10851095
...(params || {}),
@@ -1151,11 +1161,10 @@ export default class Server {
11511161
delete query._nextDataReq
11521162

11531163
const locale = query.__nextLocale as string
1154-
const locales = query.__nextLocales as string[]
1155-
// const defaultLocale = query.__nextDefaultLocale as string
11561164
delete query.__nextLocale
1157-
delete query.__nextLocales
1158-
// delete query.__nextDefaultLocale
1165+
1166+
const { i18n } = this.nextConfig.experimental
1167+
const locales = i18n.locales as string[]
11591168

11601169
let previewData: string | false | object | undefined
11611170
let isPreviewMode = false

0 commit comments

Comments
 (0)