How to Fix: Domain routing i18n adds default locale to link href’s

5 min read

When domain-based i18n routing is configured, links can unexpectedly render the default locale inside the generated href, even though that locale should stay hidden on its mapped domain. The result is incorrect URLs, duplicate locale prefixes, and navigation that no longer matches the intended domain strategy.

Understanding the Root Cause

This bug appears when internationalized routing combines domain routing with a default locale. In this setup, one domain is usually mapped to one locale, and the default locale is expected to be served without a locale prefix in the URL. For example, a page on the English domain should resolve to /about, not /en/about.

The problem happens because link generation logic may normalize routes using the active locale instead of the domain-localized URL policy. In other words, the router knows the current locale is en, but it does not always suppress that locale when building the href for a domain where en is already implied.

Technically, the mismatch usually comes from one of these conditions:

  • The Link component receives a locale-aware route and serializes it with the locale prefix before the domain mapping removes it.
  • The app mixes locale-prefixed path assumptions with domain-level locale resolution.
  • Navigation is generated from pathname helpers that do not distinguish between sub-path routing and domain routing.
  • The current locale is passed explicitly to links, forcing the framework to preserve it in the final URL.

So the root cause is not just “a wrong link.” It is a conflict between locale serialization and domain-based default locale hiding.

Step-by-Step Solution

The safest fix is to ensure that links are generated according to the active domain strategy and that the default locale is not explicitly injected into href values when the domain already represents that locale.

1. Verify your i18n domain configuration

Make sure the domain config clearly maps each domain to its intended default locale.

module.exports = {
  i18n: {
    locales: ['en', 'fr'],
    defaultLocale: 'en',
    domains: [
      {
        domain: 'example.com',
        defaultLocale: 'en'
      },
      {
        domain: 'example.fr',
        defaultLocale: 'fr'
      }
    ]
  }
}

This tells the router that example.com already implies en, so links on that domain should not need an /en prefix.

2. Avoid hardcoding locale-prefixed href values

If you manually build links like /en/about, the router cannot safely strip the prefix in every case. Use route paths that are locale-neutral.

// Avoid
<Link href="/en/about">About</Link>

// Prefer
<Link href="/about">About</Link>

This keeps the routing layer in control of locale resolution.

3. Do not force the locale prop unless you are intentionally switching locales

Passing the current locale into Link can cause the framework to keep that locale in the generated href.

// Can trigger unwanted locale prefixing
<Link href="/about" locale="en">About</Link>

// Better for same-locale navigation on a domain-mapped locale
<Link href="/about">About</Link>

Only use the locale prop when the user is actually switching to another locale.

If your app has many links, create a helper that removes the default locale prefix for the current domain. This prevents inconsistent behavior across components.

const domainLocaleMap = {
  'example.com': 'en',
  'example.fr': 'fr'
}

export function buildLocalizedHref(path, locale, host) {
  const domainDefaultLocale = domainLocaleMap[host]

  if (locale === domainDefaultLocale) {
    return path.startsWith('/') ? path : `/${path}`
  }

  const normalizedPath = path.startsWith('/') ? path : `/${path}`
  return `/${locale}${normalizedPath}`
}

Usage:

const href = buildLocalizedHref('/about', currentLocale, window.location.host)

This approach is especially useful when you generate navigation from CMS data or shared route utilities.

5. Validate rendered anchor tags, not just router state

This bug is visible in the final href output. Inspect the rendered anchor in the browser and confirm the result matches domain expectations.

<Link href="/about">About</Link>

// Expected on example.com
<a href="/about">About</a>

// Expected on example.fr for French default domain navigation
<a href="/about">About</a>

If you are linking to a non-default locale on the current domain, then a prefixed path may still be valid depending on your routing strategy.

6. Add regression tests

Because this issue affects generated markup, it is worth testing the actual href values.

import { render, screen } from '@testing-library/react'
import Link from 'next/link'

function Nav() {
  return <Link href="/about">About</Link>
}

test('does not prefix default locale on mapped domain', () => {
  render(<Nav />)
  expect(screen.getByRole('link', { name: 'About' })).toHaveAttribute('href', '/about')
})

For full confidence, also test with environment-specific hostnames or E2E tooling.

Common Edge Cases

  • Locale switchers: A language switcher should often pass locale explicitly, but regular navigation should not. Mixing those patterns can create inconsistent hrefs.
  • Custom rewrites: If you use rewrites or middleware, they can mask the issue during navigation while the anchor still contains the wrong URL.
  • SSR versus client rendering: The host or locale may differ between server and client in development, especially behind proxies, causing different href output.
  • Shared route helpers: Utility functions written for sub-path i18n routing may incorrectly add locale prefixes in a domain-routing setup.
  • Canonical URLs and SEO: If the default locale appears in hrefs when it should not, you can accidentally create duplicate URLs that split indexing signals.
  • Cross-domain locale navigation: Switching from one locale domain to another may require absolute URL handling instead of simple pathname changes.

FAQ

Why does this only happen with domain routing?

Because domain routing changes the rule for how locale information is represented. With sub-path routing, /en/about may be correct. With domain routing, the domain itself already identifies the locale, so the default locale prefix should usually be omitted.

No. Remove it for normal navigation within the current locale/domain. Keep it only when you intentionally want to switch locales. The key is to avoid forcing the current default locale into the generated href.

Can this affect SEO?

Yes. Incorrectly prefixed default-locale URLs can create duplicate content paths, inconsistent internal linking, and weaker canonicalization. Review your rendered links, canonical tags, and hreflang setup together.

The durable fix is to treat domain-mapped default locales as implicit, keep href values locale-neutral for same-locale navigation, and only inject locale information when the user is explicitly changing language context.

Leave a Reply

Your email address will not be published. Required fields are marked *