Locale specific domains in Next.js with next-i18next
Tags: nextjs, i18n, seo
Published: 2020-08-18 17:12:42

Google recommends three different ways for creating [locale-specific URLs](https://support.google.com/webmasters/answer/182192?hl=en#locale-specific-urls): 

1. Country-specific domains: `example.de`
2. Subdomains: `de.example.com`
3. Subdirectories: `examples.com/de/`

In this post, I'll show how to use country-specific domains and subdomains for language detection in [Next.js](https://github.com/vercel/next.js) with [next-i18next](https://github.com/isaachinman/next-i18next).

> A full working example of what will be shown is available here: https://github.com/justincy/nextjs-domain-locales

By default, next-i18next uses the `Accept-Language` header (sent by the browser) to determine which language to show initially. This choice is stored in a cookie so that if the user requests a different language, that new preference is persisted.

But when using locale-specific domains and subdomains, we don't care about the `Accept-Language` header. We just need to know what domain is being used so that we can lookup the associated locale. Thankfully, next-i18next allows for custom language detectors. So let's create one.

First, we need a list of the domains we want to use as well as their associated locales. You can use any domain names you want as long as they get routed to your server and you configure them in the locale map.

```js
const domainLocaleMap = {
  'localhost': 'en',
  'nexttest.com': 'en',
  'nexttest.es': 'es',
  'nexttest.de': 'de',
  'it.nexttest.international': 'it',
  'ua.nexttest.international': 'ua',
};
```

> You'll need to [modify your hosts file](https://www.hostgator.com/help/article/how-do-i-change-my-hosts-file) to get those domains working locally.

Now we need to write the code that uses the `domainLocaleMap`. For this we'll be writing a [custom i18next-http-middleware language detector](https://github.com/i18next/i18next-http-middleware#interface). We don't need to cache the language (because it's always determined by the URL) so we just need to specify name and lookup. The code will run both in the browser and on the server so we handle both cases.

```js
const domainDetector = {
  // We use the name to refer to it later when we want to tell i18next when to use it.
  name: 'domain',
  lookup(req, res, options) {
    // You need to either set the value of DEFAULT_LOCALE before this or hard-code the value here.
    let locale = DEFAULT_LOCALE;
    // In the browser, get the hostname from window.location.
    if (typeof window !== 'undefined' ) {
      locale = domainLocaleMap[window.location.hostname];
    } 
    // On the server, get the hostname from the request headers.
    // We use the host header which is available on IncomingMessage.
    // https://nodejs.org/api/http.html#http_class_http_incomingmessage
    // But the host header may include the port so first we take that off, if it exists.
    else {
      const hostname = req.headers.host?.split(':')[0];
      locale = domainLocaleMap[hostname];
    }
    return locale;
  }
};
```

Now all that's left is to register the custom detector.

```js
// https://github.com/isaachinman/next-i18next#options
module.exports = new NextI18Next({
  defaultLanguage: DEFAULT_LOCALE,
  otherLanguages: supportedLangs,
  localePath: path.resolve('./public/static/locales'),
  // Register our custom language detector
  customDetectors: [domainDetector],
  // Tell i18next to _only_ use our custom language detector.
  // https://github.com/i18next/i18next-http-middleware#detector-options
  detection: {
    order: ['domain']
  }
});
```

That's it! A full working example with Next.js is available here: https://github.com/justincy/nextjs-domain-locales

> Hat tip to [@lucasfeliciano](https://github.com/lucasfeliciano) for providing most of the [details about how this can be done](https://github.com/isaachinman/next-i18next/issues/16#issuecomment-520853281).