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

Update redirect handling for locale domains #17856

Merged
merged 3 commits into from Oct 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 54 additions & 23 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -222,64 +222,95 @@ const nextServerlessLoader: loader.Loader = function () {
const i18n = ${i18n}
const accept = require('@hapi/accept')
const { detectLocaleCookie } = require('next/dist/next-server/lib/i18n/detect-locale-cookie')
const { detectDomainLocales } = require('next/dist/next-server/lib/i18n/detect-domain-locales')
const { detectDomainLocale } = require('next/dist/next-server/lib/i18n/detect-domain-locale')
const { normalizeLocalePath } = require('next/dist/next-server/lib/i18n/normalize-locale-path')
let locales = i18n.locales
let defaultLocale = i18n.defaultLocale
let detectedLocale = detectLocaleCookie(req, i18n.locales)

const { defaultLocale, locales } = detectDomainLocales(
req,
const detectedDomain = detectDomainLocale(
i18n.domains,
i18n.locales,
i18n.defaultLocale,
req,
)
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
detectedLocale = defaultLocale
}

if (!detectedLocale) {
detectedLocale = accept.language(
req.headers['accept-language'],
locales
i18n.locales
)
}

let localeDomainRedirect
const localePathResult = normalizeLocalePath(parsedUrl.pathname, i18n.locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsedUrl,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname

// check if the locale prefix matches a domain's defaultLocale
// and we're on a locale specific domain if so redirect to that domain
if (detectedDomain) {
const matchedDomain = detectDomainLocale(
i18n.domains,
undefined,
detectedLocale
)

if (matchedDomain) {
localeDomainRedirect = \`http\${
matchedDomain.http ? '' : 's'
}://\${matchedDomain.domain}\`
}
}
}

const denormalizedPagePath = denormalizePagePath(parsedUrl.pathname || '/')
const detectedDefaultLocale = !detectedLocale || detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const detectedDefaultLocale =
!detectedLocale ||
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const shouldStripDefaultLocale =
detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === \`/\${defaultLocale.toLowerCase()}\`
denormalizedPagePath.toLowerCase() === \`/\${i18n.defaultLocale.toLowerCase()}\`
const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/'

detectedLocale = detectedLocale || defaultLocale
detectedLocale = detectedLocale || i18n.defaultLocale

if (
!fromExport &&
!nextStartMode &&
i18n.localeDetection !== false &&
(shouldAddLocalePrefix || shouldStripDefaultLocale)
(
localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale
)
) {
res.setHeader(
'Location',
formatUrl({
// make sure to include any query values when redirecting
...parsedUrl,
pathname: shouldStripDefaultLocale ? '/' : \`/\${detectedLocale}\`,
pathname:
localeDomainRedirect
? localeDomainRedirect
: shouldStripDefaultLocale
? '/'
: \`/\${detectedLocale}\`,
})
)
res.statusCode = 307
res.end()
return
}

const localePathResult = normalizeLocalePath(parsedUrl.pathname, locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsedUrl,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname
}

detectedLocale = detectedLocale || defaultLocale
`
: `
Expand Down
41 changes: 41 additions & 0 deletions packages/next/next-server/lib/i18n/detect-domain-locale.ts
@@ -0,0 +1,41 @@
import { IncomingMessage } from 'http'

export function detectDomainLocale(
domainItems:
| Array<{
http?: boolean
domain: string
defaultLocale: string
}>
| undefined,
req?: IncomingMessage,
detectedLocale?: string
) {
let domainItem:
| {
http?: boolean
domain: string
defaultLocale: string
}
| undefined

if (domainItems) {
const { host } = req?.headers || {}
// remove port from host and remove port if present
const hostname = host?.split(':')[0].toLowerCase()

for (const item of domainItems) {
// remove port if present
const domainHostname = item.domain?.split(':')[0].toLowerCase()
if (
hostname === domainHostname ||
detectedLocale?.toLowerCase() === item.defaultLocale.toLowerCase()
) {
domainItem = item
break
}
}
}

return domainItem
}
37 changes: 0 additions & 37 deletions packages/next/next-server/lib/i18n/detect-domain-locales.ts

This file was deleted.

15 changes: 0 additions & 15 deletions packages/next/next-server/server/config.ts
Expand Up @@ -238,22 +238,7 @@ function assignDefaults(userConfig: { [key: string]: any }) {
if (!item || typeof item !== 'object') return true
if (!item.defaultLocale) return true
if (!item.domain || typeof item.domain !== 'string') return true
if (!item.locales || !Array.isArray(item.locales)) return true

const invalidLocales = item.locales.filter(
(locale: string) => !i18n.locales.includes(locale)
)

if (invalidLocales.length > 0) {
console.error(
`i18n.domains item "${
item.domain
}" has the following locales (${invalidLocales.join(
', '
)}) 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`
)
return true
}
return false
})

Expand Down
97 changes: 53 additions & 44 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -79,7 +79,7 @@ import accept from '@hapi/accept'
import { normalizeLocalePath } from '../lib/i18n/normalize-locale-path'
import { detectLocaleCookie } from '../lib/i18n/detect-locale-cookie'
import * as Log from '../../build/output/log'
import { detectDomainLocales } from '../lib/i18n/detect-domain-locales'
import { detectDomainLocale } from '../lib/i18n/detect-domain-locale'

const getCustomRouteMatcher = pathMatch(true)

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

const { defaultLocale, locales } = detectDomainLocales(
req,
i18n.domains,
i18n.locales,
i18n.defaultLocale
)
const detectedDomain = detectDomainLocale(i18n.domains, req)
if (detectedDomain) {
defaultLocale = detectedDomain.defaultLocale
detectedLocale = defaultLocale
}

if (!detectedLocale) {
detectedLocale = accept.language(
req.headers['accept-language'],
locales
i18n.locales
)
}

let localeDomainRedirect: string | undefined
const localePathResult = normalizeLocalePath(pathname!, i18n.locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsed,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname

// check if the locale prefix matches a domain's defaultLocale
// and we're on a locale specific domain if so redirect to that domain
if (detectedDomain) {
const matchedDomain = detectDomainLocale(
i18n.domains,
undefined,
detectedLocale
)

if (matchedDomain) {
localeDomainRedirect = `http${matchedDomain.http ? '' : 's'}://${
matchedDomain?.domain
}`
}
}
}

const denormalizedPagePath = denormalizePagePath(pathname || '/')
const detectedDefaultLocale =
!detectedLocale ||
detectedLocale.toLowerCase() === defaultLocale.toLowerCase()
const shouldStripDefaultLocale =
detectedDefaultLocale &&
denormalizedPagePath.toLowerCase() === `/${defaultLocale.toLowerCase()}`
denormalizedPagePath.toLowerCase() ===
`/${i18n.defaultLocale.toLowerCase()}`
const shouldAddLocalePrefix =
!detectedDefaultLocale && denormalizedPagePath === '/'

detectedLocale = detectedLocale || defaultLocale
detectedLocale = detectedLocale || i18n.defaultLocale

if (
i18n.localeDetection !== false &&
(shouldAddLocalePrefix || shouldStripDefaultLocale)
(localeDomainRedirect ||
shouldAddLocalePrefix ||
shouldStripDefaultLocale)
) {
res.setHeader(
'Location',
formatUrl({
// make sure to include any query values when redirecting
...parsed,
pathname: shouldStripDefaultLocale ? '/' : `/${detectedLocale}`,
pathname: localeDomainRedirect
? localeDomainRedirect
: shouldStripDefaultLocale
? '/'
: `/${detectedLocale}`,
})
)
res.statusCode = 307
res.end()
return
}

const localePathResult = normalizeLocalePath(pathname!, locales)

if (localePathResult.detectedLocale) {
detectedLocale = localePathResult.detectedLocale
req.url = formatUrl({
...parsed,
pathname: localePathResult.pathname,
})
parsedUrl.pathname = localePathResult.pathname
}

// TODO: render with domain specific locales and defaultLocale also?
// Currently locale specific domains will have all locales populated
// under router.locales instead of only the domain specific ones
parsedUrl.query.__nextLocales = i18n.locales
// parsedUrl.query.__nextDefaultLocale = defaultLocale
parsedUrl.query.__nextLocale = detectedLocale || defaultLocale
}

Expand Down Expand Up @@ -517,21 +535,15 @@ export default class Server {

if (i18n) {
const localePathResult = normalizeLocalePath(pathname, i18n.locales)
const { defaultLocale } = detectDomainLocales(
req,
i18n.domains,
i18n.locales,
i18n.defaultLocale
)
const { defaultLocale } =
detectDomainLocale(i18n.domains, req) || {}
let detectedLocale = defaultLocale

if (localePathResult.detectedLocale) {
pathname = localePathResult.pathname
detectedLocale = localePathResult.detectedLocale
}
_parsedUrl.query.__nextLocales = i18n.locales
_parsedUrl.query.__nextLocale = detectedLocale
// _parsedUrl.query.__nextDefaultLocale = defaultLocale
_parsedUrl.query.__nextLocale = detectedLocale!
}
pathname = getRouteFromAssetPath(pathname, '.json')

Expand Down Expand Up @@ -1078,8 +1090,6 @@ export default class Server {
amp: query.amp,
_nextDataReq: query._nextDataReq,
__nextLocale: query.__nextLocale,
__nextLocales: query.__nextLocales,
// __nextDefaultLocale: query.__nextDefaultLocale,
}
: query),
...(params || {}),
Expand Down Expand Up @@ -1151,11 +1161,10 @@ export default class Server {
delete query._nextDataReq

const locale = query.__nextLocale as string
const locales = query.__nextLocales as string[]
// const defaultLocale = query.__nextDefaultLocale as string
delete query.__nextLocale
delete query.__nextLocales
// delete query.__nextDefaultLocale

const { i18n } = this.nextConfig.experimental
const locales = i18n.locales as string[]

let previewData: string | false | object | undefined
let isPreviewMode = false
Expand Down