Multi domain locales
How to set up multi domain locales:
- Set the
multiDomainLocalesoption totrue - Configure the
localesoption as an array of objects:- Each object has a
domainskey whose value is a array of the domains you'd like to use for that locale. Optionally include a port (if non-standard) and/or a protocol. If the protocol is not provided then an attempt will be made to auto-detect it but that might not work correctly in some cases like when the pages are statically generated. - Optionally set for each object a
defaultForDomainskey whose value is a array of the default domains you'd like to use for that locale. Optionally include a port (if non-standard) and/or a protocol. If the protocol is not provided then an attempt will be made to auto-detect it but that might not work correctly in some cases like when the pages are statically generated.
- Each object has a
- Optionally set
defaultLocale. Each domain resolves its own unprefixed locale fromdefaultForDomains, this names the fallback for the cluster as a whole - seedefaultLocaleandx-default. - Optionally set
detectBrowserLanguagetofalse. When enabled (which it is by default), a first visit is redirected to the domain serving the locale detected from the browser, while a visitor arriving from one of your own domains (a locale switcher link, a cross-domain link) stays on the domain they chose. A crawler sending anAccept-Languageheader (Bingbot does, Googlebot mostly doesn't) counts as such a first visit and is answered with a redirect instead of content on domains that don't serve its language. Set tofalseif you want to ensure that visiting a given domain always shows the page in that domain's own locale, crawlers included. Build cross-domain switchers with<SwitchLocalePathLink>- a plain<NuxtLink>to another domain carriesrel="noreferrer", so the arrival cannot be recognized as one of your own and is redirected by detection again. A site-wideReferrer-Policythat strips the referer has the same effect - there, only acookieDomainspanning the domains keeps switches sticky. - When your domains share a suffix (e.g. subdomains of one site), set
detectBrowserLanguage.cookieDomainto that suffix so the visitor's locale choice travels between the domains. A cookie scoped to a single domain is only applied on the domain that set it, it never redirects to another domain - the same applies to acookieDomainthat doesn't cover every configured domain.
const i18nDomains = ['mydomain.com', 'es.mydomain.com', 'fr.mydomain.com', 'http://pl.mydomain.com', 'https://ua.mydomain.com']
export default defineNuxtConfig({
i18n: {
locales: [
{
code: 'en',
domains: i18nDomains,
defaultForDomains: ['mydomain.com']
},
{
code: 'es',
domains: i18nDomains,
defaultForDomains: ['es.mydomain.com']
},
{
code: 'fr',
domains: i18nDomains,
defaultForDomains: ['fr.mydomain.com']
},
{
code: 'pl',
domains: i18nDomains,
defaultForDomains: ['http://pl.mydomain.com']
},
{
code: 'ua',
domains: i18nDomains,
defaultForDomains: ['https://ua.mydomain.com']
},
{
code: 'nl',
domains: i18nDomains
},
{
code: 'de',
domains: i18nDomains
},
],
defaultLocale: 'en',
multiDomainLocales: true
}
})
Runtime environment variables
Sometimes there's a need to change domains in different environments, e.g. staging and production.
As nuxt.config.ts is used at build time it would be necessary to create different builds for different environments.
export const localeDomains = {
uk: process.env.DOMAIN_UK,
fr: process.env.DOMAIN_FR
}
import { localeDomains } from './locale-domains.config'
const i18nDomains = [localeDomains.uk, localeDomains.fr]
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
multiDomainLocales: true,
locales: [
{
code: 'uk',
domains: i18nDomains,
defaultForDomains: [localeDomains.uk]
},
{
code: 'fr',
domains: i18nDomains,
defaultForDomains: [localeDomains.fr]
}
]
}
})
With the above config, a build would have to be run for staging and production with different .env files that specify DOMAIN_UK and DOMAIN_FR.
A single build can also serve different domains by overriding them with NUXT_PUBLIC_I18N_DOMAIN_LOCALES_{code}_DOMAIN at runtime - see runtime environment variables in the differentDomains guide, including what the override cannot change.
Using different domains for only some of the languages
If multiple domains share the same default language, you can specify them all using defaultForDomains, which supports multiple domains.
const i18nDomains = ['mydomain.com', 'en.mydomain.com', 'es.mydomain.com', 'fr.mydomain.com', 'http://pl.mydomain.com', 'https://ua.mydomain.com']
export default defineNuxtConfig({
// ...
i18n: {
locales: [
{
code: 'en',
domains: i18nDomains,
defaultForDomains: ['mydomain.com', 'en.mydomain.com']
},
{
code: 'es',
domains: i18nDomains,
defaultForDomains: ['es.mydomain.com']
},
{
code: 'fr',
domains: i18nDomains,
defaultForDomains: ['fr.mydomain.com']
},
{
code: 'pl',
domains: i18nDomains,
defaultForDomains: ['http://pl.mydomain.com']
},
{
code: 'ua',
domains: i18nDomains,
defaultForDomains: ['https://ua.mydomain.com']
},
{
code: 'nl',
domains: i18nDomains
},
{
code: 'de',
domains: i18nDomains
},
],
strategy: 'prefix',
multiDomainLocales: true
},
// ...
})
Given above configuration with the 'prefix' strategy, following requests will be:
- https://mydomain.com -> https://mydomain.com/en (en language)
- https://mydomain.com/pl -> https://mydomain.com/pl (pl language)
- https://mydomain.com/ua -> https://mydomain.com/ua (ua language)
- https://mydomain.com/nl -> https://mydomain.com/nl (nl language)
- https://en.mydomain.com -> https://en.mydomain.com/en (en language)
- https://es.mydomain.com -> https://es.mydomain.com/es (es language)
- https://fr.mydomain.com -> https://fr.mydomain.com/fr (fr language)
- https://fr.mydomain.com/de -> https://fr.mydomain.com/de (de language)
The same requests when using the 'prefix_except_default' strategy, will be:
- https://mydomain.com -> https://mydomain.com (en language)
- https://mydomain.com/pl -> https://mydomain.com/pl (pl language)
- https://mydomain.com/ua -> https://mydomain.com/ua (ua language)
- https://mydomain.com/nl -> https://mydomain.com/nl (nl language)
- https://en.mydomain.com -> https://en.mydomain.com (en language)
- https://es.mydomain.com -> https://es.mydomain.com (es language)
- https://fr.mydomain.com -> https://fr.mydomain.com (fr language)
- https://fr.mydomain.com/de -> https://fr.mydomain.com/de (de language)
- https://fr.mydomain.com/en -> https://fr.mydomain.com/en (en language)
A locale is only unprefixed on the domains it is the default for, on every other domain it keeps its prefix. This includes the locale set as defaultLocale.
Restricting locales to specific domains
A locale's domains doesn't have to list every domain, it only has to list the domains that locale should be served on. A locale configured without domains is served on all of them.
Restricting locales is what keeps each page reachable at a single address: a locale listed on one domain is served there and relocated there from the others. Listing a locale on several domains serves it on each of them, so the same page becomes reachable at several URLs - see defaultLocale and x-default for how those are annotated.
Restricting every locale to a domain of its own is also what makes strategy: 'no_prefix' usable here: with no prefix in the path, the domain is the only thing naming a locale, so each domain has to serve exactly one. Two locales sharing a domain under 'no_prefix' leaves routes unlocalized and is reported at build time.
export default defineNuxtConfig({
i18n: {
locales: [
{
code: 'en',
domains: ['mydomain.com'],
defaultForDomains: ['mydomain.com']
},
{
// shared between both domains
code: 'fr',
domains: ['mydomain.com', 'es.mydomain.com']
},
{
code: 'es',
domains: ['es.mydomain.com'],
defaultForDomains: ['es.mydomain.com']
}
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
multiDomainLocales: true
}
})
Given the above configuration, following requests will be:
- https://mydomain.com -> https://mydomain.com (en language)
- https://mydomain.com/fr -> https://mydomain.com/fr (fr language)
- https://mydomain.com/es -> https://es.mydomain.com (es language, redirected to the domain serving it)
- https://es.mydomain.com -> https://es.mydomain.com (es language)
- https://es.mydomain.com/fr -> https://es.mydomain.com/fr (fr language)
- https://es.mydomain.com/en -> https://mydomain.com (en language, redirected to the domain serving it)
Locales served on other domains stay available in locales and in the locale switcher, switchLocalePath links to them on the domain that serves them. Browser language detection follows the same rule: a first visit whose detected locale is served on another domain is redirected to that domain. A visitor arriving from one of the configured domains already chose their destination and keeps that domain's own locale, and a locale cookie is only followed to another domain when detectBrowserLanguage.cookieDomain makes it visible there.
A request on a host that doesn't match any configured domain, such as a staging domain or a health check by IP, isn't restricted. Every locale is served there and defaultLocale is used as the unprefixed default, so it's worth setting even when every domain has its own default through defaultForDomains. Redirects on such a host stay relative, they never send a visitor to one of the configured domains.
defaultLocale and x-default
The domains are annotated as one cluster, each page links to its alternates on the other domains. A cluster has a single fallback for unmatched languages, so the x-default alternate is taken from defaultLocale rather than from the locale a domain happens to default to - otherwise every domain would name a different one.
A locale served on several domains is annotated on one of them: the first entry of its defaultForDomains, or of its domains otherwise. Every domain that serves the locale emits that same URL for it, keeping the cluster reciprocal - annotating the current domain instead would make each domain claim the language for itself. A locale configured without any domain is served on all of them and has none of its own to be annotated on, so it takes the domain serving defaultLocale.
The canonical link follows the same rule, so a page reachable on several domains points at the URL its language is advertised on rather than claiming itself. A locale served on a single domain canonicalises to that domain, as before.
This means a locale you list on several domains is advertised at one of them while remaining reachable at the others, so restrict a locale to the domain it belongs on unless you deliberately want it served in several places.
defaultLocale is optional here, since each domain resolves its own unprefixed locale through defaultForDomains. Leaving it out means no x-default is annotated at all, which is allowed but drops a signal for visitors whose language matches none of your locales. A warning is logged when it isn't set.