Skip to content

Commit

Permalink
fix: invalid canonical SEO link with differentDomains (#1049)
Browse files Browse the repository at this point in the history
  • Loading branch information
rchl committed Feb 1, 2021
1 parent 8f4cef5 commit d05317b
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 31 deletions.
26 changes: 16 additions & 10 deletions src/templates/head-meta.js
Expand Up @@ -85,7 +85,7 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false
link.push({
hid: `i18n-alt-${iso}`,
rel: 'alternate',
href: baseUrl + this.switchLocalePath(mapLocale.code),
href: toAbsoluteUrl(this.switchLocalePath(mapLocale.code), baseUrl),
hreflang: iso
})
}
Expand All @@ -94,7 +94,7 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false
link.push({
hid: 'i18n-xd',
rel: 'alternate',
href: baseUrl + this.switchLocalePath(defaultLocale),
href: toAbsoluteUrl(this.switchLocalePath(defaultLocale), baseUrl),
hreflang: 'x-default'
})
}
Expand All @@ -105,17 +105,16 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false
...this.$route,
name: this.getRouteBaseName()
})

const canonicalPath = currentRoute ? currentRoute.path : null

if (!canonicalPath) {
return
if (canonicalPath) {
link.push({
hid: 'i18n-can',
rel: 'canonical',
href: toAbsoluteUrl(canonicalPath, baseUrl)
})
}

link.push({
hid: 'i18n-can',
rel: 'canonical',
href: baseUrl + canonicalPath
})
}

function addCurrentOgLocale (currentLocale, currentLocaleIso, meta) {
Expand Down Expand Up @@ -156,6 +155,13 @@ export function nuxtI18nHead ({ addDirAttribute = true, addSeoAttributes = false
return isoFromLocale(locale).replace(/-/g, '_')
}

function toAbsoluteUrl (urlOrPath, baseUrl) {
if (urlOrPath.match(/^https?:\/\//)) {
return urlOrPath
}
return baseUrl + urlOrPath
}

return metaObject
}

Expand Down
8 changes: 5 additions & 3 deletions src/templates/plugin.main.js
Expand Up @@ -189,7 +189,8 @@ export default async (context) => {
return [302, storedRedirect]
}

app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
const options = { differentDomains, locales, localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY, moduleName: MODULE_NAME }
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context, app.i18n.locale, options)

const finalLocale =
(detectBrowserLanguage && doDetectBrowserLanguage(route)) ||
Expand Down Expand Up @@ -273,7 +274,8 @@ export default async (context) => {
app.i18n.localeProperties = { code: '' }
app.i18n.fallbackLocale = vueI18nOptions.fallbackLocale || ''
extendVueI18nInstance(app.i18n)
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context)
const options = { differentDomains, locales, localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY, moduleName: MODULE_NAME }
app.i18n.__baseUrl = resolveBaseUrl(baseUrl, context, '', options)
app.i18n.__onNavigate = onNavigate

Vue.prototype.$nuxtI18nSeo = nuxtI18nSeo
Expand All @@ -296,7 +298,7 @@ export default async (context) => {
if (vuex && vuex.syncLocale && store && store.state[vuex.moduleName].locale !== '') {
finalLocale = store.state[vuex.moduleName].locale
} else if (app.i18n.differentDomains) {
const options = { localDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY }
const options = { localeDomainKey: LOCALE_DOMAIN_KEY, localeCodeKey: LOCALE_CODE_KEY }
const domainLocale = getLocaleDomain(locales, req, options)
finalLocale = domainLocale
} else if (strategy !== STRATEGIES.NO_PREFIX) {
Expand Down
25 changes: 12 additions & 13 deletions src/templates/plugin.routing.js
Expand Up @@ -12,6 +12,7 @@ import {
trailingSlash,
vuex
} from './options'
import { getDomainFromLocale } from './utils-common'

function localePath (route, locale) {
const localizedRoute = localeRoute.call(this, route, locale)
Expand Down Expand Up @@ -109,21 +110,19 @@ function switchLocalePath (locale) {

// Handle different domains
if (i18n.differentDomains) {
const lang = i18n.locales.find(l => l[LOCALE_CODE_KEY] === locale)
if (lang && lang[LOCALE_DOMAIN_KEY]) {
let protocol
if (process.server) {
const isHTTPS = require('is-https')
protocol = (this.req && isHTTPS(this.req)) ? 'https' : 'http'
} else {
protocol = window.location.protocol.split(':')[0]
}
path = protocol + '://' + lang[LOCALE_DOMAIN_KEY] + path
} else {
// eslint-disable-next-line no-console
console.warn(`[${MODULE_NAME}] Could not find domain name for locale ${locale}`)
const options = {
differentDomains: i18n.differentDomains,
locales: i18n.locales,
localeDomainKey: LOCALE_DOMAIN_KEY,
localeCodeKey: LOCALE_CODE_KEY,
moduleName: MODULE_NAME
}
const domain = getDomainFromLocale(locale, this.req, options)
if (domain) {
path = domain + path
}
}

return path
}

Expand Down
44 changes: 40 additions & 4 deletions src/templates/utils-common.js
Expand Up @@ -70,24 +70,60 @@ export const matchBrowserLocale = (appLocales, browserLocales) => {
* Resolves base URL value if provided as function. Otherwise just returns verbatim.
* @param {string | function} baseUrl
* @param {import('@nuxt/types').Context} context
* @param {import('../../types').NuxtVueI18n.Locale} localeCode
* @param {object} options
* @return {string}
*/
export const resolveBaseUrl = (baseUrl, context) => {
export const resolveBaseUrl = (baseUrl, context, localeCode, { differentDomains, locales, localeDomainKey, localeCodeKey, moduleName }) => {
if (typeof baseUrl === 'function') {
return baseUrl(context)
}

if (differentDomains && localeCode) {
// Lookup the `differentDomain` origin associated with given locale.
const domain = getDomainFromLocale(localeCode, context.req, { locales, localeDomainKey, localeCodeKey, moduleName })
if (domain) {
return domain
}
}

return baseUrl
}

/**
* Gets the `differentDomain` domain from locale.
*
* @param {string} localeCode The locale code
* @param {import('connect').IncomingMessage} [req] Request object
* @param {object} options
* @return {string | undefined}
*/
export const getDomainFromLocale = (localeCode, req, { locales, localeDomainKey, localeCodeKey, moduleName }) => {
// Lookup the `differentDomain` origin associated with given locale.
const lang = locales.find(locale => locale[localeCodeKey] === localeCode)
if (lang && lang[localeDomainKey]) {
let protocol
if (process.server) {
const isHTTPS = require('is-https')
protocol = (req && isHTTPS(req)) ? 'https' : 'http'
} else {
protocol = window.location.protocol.split(':')[0]
}
return `${protocol}://${lang[localeDomainKey]}`
}

// eslint-disable-next-line no-console
console.warn(`[${moduleName}] Could not find domain name for locale ${localeCode}`)
}

/**
* Get locale code that corresponds to current hostname
* @param {object} locales
* @param {object} [req] Request object
* @param {{ localDomainKey: string, localeCodeKey: string }} options
* @param {{ localeDomainKey: string, localeCodeKey: string }} options
* @return {string | null} Locade code found if any
*/
export const getLocaleDomain = (locales, req, { localDomainKey, localeCodeKey }) => {
export const getLocaleDomain = (locales, req, { localeDomainKey, localeCodeKey }) => {
let host = null

if (process.client) {
Expand All @@ -97,7 +133,7 @@ export const getLocaleDomain = (locales, req, { localDomainKey, localeCodeKey })
}

if (host) {
const matchingLocale = locales.find(l => l[localDomainKey] === host)
const matchingLocale = locales.find(l => l[localeDomainKey] === host)
if (matchingLocale) {
return matchingLocale[localeCodeKey]
}
Expand Down
2 changes: 1 addition & 1 deletion test/fixture/basic/pages/locale.vue
Expand Up @@ -5,7 +5,7 @@
<script>
export default {
head () {
return this.$nuxtI18nHead()
return this.$nuxtI18nHead({ addSeoAttributes: true })
}
}
</script>
61 changes: 61 additions & 0 deletions test/module.test.js
Expand Up @@ -147,6 +147,67 @@ describe('differentDomains enabled', () => {
const dom = getDom(html)
expect(dom.documentElement.getAttribute('dir')).toEqual('auto')
})

test('dir and SEO attributes exists', async () => {
const requestOptions = {
headers: {
Host: 'en.nuxt-app.localhost'
}
}
const html = await get('/locale', requestOptions)
const dom = getDom(html)
expect(dom.documentElement.getAttribute('dir')).toEqual('ltr')

const seoTags = getSeoTags(dom)
const expectedSeoTags = [
{
tagName: 'meta',
property: 'og:locale',
content: 'en_US'
},
{
tagName: 'meta',
property: 'og:locale:alternate',
content: 'fr_FR'
},
{
tagName: 'link',
rel: 'alternate',
href: 'http://en.nuxt-app.localhost/locale',
hreflang: 'en'
},
{
tagName: 'link',
rel: 'alternate',
href: 'http://en.nuxt-app.localhost/locale',
hreflang: 'en-US'
},
{
tagName: 'link',
rel: 'alternate',
href: 'http://fr.nuxt-app.localhost/locale',
hreflang: 'fr'
},
{
tagName: 'link',
rel: 'alternate',
href: 'http://fr.nuxt-app.localhost/locale',
hreflang: 'fr-FR'
},
{
tagName: 'link',
rel: 'alternate',
href: 'http://en.nuxt-app.localhost/locale',
hreflang: 'x-default'
},
{
tagName: 'link',
rel: 'canonical',
href: 'http://en.nuxt-app.localhost/locale'
}
]
expect(seoTags).toEqual(expectedSeoTags)
})
})

const TRAILING_SLASHES = [undefined, false, true]
Expand Down

0 comments on commit d05317b

Please sign in to comment.