Nuxt i18n
Guide

Multi domain locales

Set up multiple domains for multiple locales. Use a different domain name for each language your app supports.

How to set up multi domain locales:

  • Set the multiDomainLocales option to true
  • Configure the locales option as an array of objects:
    • Each object has a domains key 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 defaultForDomains key 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.
  • Optionally set defaultLocale. Each domain resolves its own unprefixed locale from defaultForDomains, this names the fallback for the cluster as a whole - see defaultLocale and x-default.
  • Optionally set detectBrowserLanguage to false. 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 an Accept-Language header (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 to false if 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 carries rel="noreferrer", so the arrival cannot be recognized as one of your own and is redirected by detection again. A site-wide Referrer-Policy that strips the referer has the same effect - there, only a cookieDomain spanning the domains keeps switches sticky.
  • When your domains share a suffix (e.g. subdomains of one site), set detectBrowserLanguage.cookieDomain to 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 a cookieDomain that doesn't cover every configured domain.
nuxt.config.ts
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.

locale-domains.config.ts
export const localeDomains = {
  uk: process.env.DOMAIN_UK,
  fr: process.env.DOMAIN_FR
}
nuxt.config.ts
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.

nuxt.config.js
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:

The same requests when using the 'prefix_except_default' strategy, will be:

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.

nuxt.config.ts
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:

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.

Copyright © 2026