vercel/next.js · error

Invalid i18n.domains values: ${invalidDomainItems.map((item:

Error message

Invalid i18n.domains values:
${invalidDomainItems.map((item: any) => JSON.stringify(item)).join('\n')}

domains value must follow format { domain: 'example.fr', defaultLocale: 'fr', locales: ['fr'] }.
See more info here: https://nextjs.org/docs/messages/invalid-i18n-config

What it means

Thrown when one or more entries in `i18n.domains` fail structural validation (config.ts:1234-1289). An item is invalid if it is not an object, lacks `defaultLocale`, lacks a string `domain`, the domain contains a `:` (protocol/port), two domains share the same `defaultLocale`, a domain's `locales` contains a non-string, or the same locale appears in two domains. The error message lists the offending items via `JSON.stringify`.

Source

Thrown at packages/next/src/server/config.ts:1282

            for (const domainItem of i18n.domains || []) {
              if (domainItem === item) continue
              if (domainItem.locales && domainItem.locales.includes(locale)) {
                console.warn(
                  `Both ${item.domain} and ${domainItem.domain} configured the locale (${locale}) but only one can. Remove it from one i18n.domains config to continue`
                )
                hasInvalidLocale = true
                break
              }
            }
          }
        }

        return hasInvalidLocale
      })

      if (invalidDomainItems.length > 0) {
        throw new Error(
          `Invalid i18n.domains values:\n${invalidDomainItems
            .map((item: any) => JSON.stringify(item))
            .join(
              '\n'
            )}\n\ndomains value must follow format { domain: 'example.fr', defaultLocale: 'fr', locales: ['fr'] }.\nSee more info here: https://nextjs.org/docs/messages/invalid-i18n-config`
        )
      }
    }

    if (!Array.isArray(i18n.locales)) {
      throw new Error(
        `Specified i18n.locales must be an array of locale strings e.g. ["en-US", "nl-NL"] received ${typeof i18n.locales}.\nSee more info here: https://nextjs.org/docs/messages/invalid-i18n-config`
      )
    }

    const invalidLocales = i18n.locales.filter(
      (locale: any) => typeof locale !== 'string'
    )

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Inspect the JSON-stringified offending items in the message and fix each reported field.
  2. Strip protocol and port from `domain` values: use `'example.fr'`, not `'https://example.fr:3000'`.
  3. Ensure each `defaultLocale` is unique across all domain entries.
  4. Ensure each locale string appears in only one domain's `locales` array.
  5. Verify every domain object has `domain` (string, no `:`), `defaultLocale`, and optionally `locales` (array of strings).

Example fix

// before
i18n: { domains: [ { domain: 'https://example.fr', defaultLocale: 'fr' }, { domain: 'example.de', defaultLocale: 'fr' } ] }
// after
i18n: { domains: [ { domain: 'example.fr', defaultLocale: 'fr', locales: ['fr'] }, { domain: 'example.de', defaultLocale: 'de', locales: ['de'] } ] }
Defensive patterns

Strategy: validation

Validate before calling

function validateDomains(domains, locales) {
  const seenDefault = new Set();
  const seenLocale = new Set();
  for (const d of domains) {
    if (!d || typeof d.domain !== 'string' || d.domain.includes(':') || !d.defaultLocale) throw new Error('bad domain entry');
    if (seenDefault.has(d.defaultLocale)) throw new Error('duplicate defaultLocale across domains');
    seenDefault.add(d.defaultLocale);
    for (const l of d.locales ?? []) {
      if (typeof l !== 'string') throw new Error('non-string locale in domain');
      if (seenLocale.has(l)) throw new Error('locale in two domains');
      seenLocale.add(l);
    }
  }
}

Type guard

function isDomainEntry(x: any): x is { domain: string; defaultLocale: string; locales?: string[] } {
  return (
    x && typeof x === 'object' &&
    typeof x.domain === 'string' && !x.domain.includes(':') &&
    !!x.defaultLocale &&
    (x.locales === undefined || (Array.isArray(x.locales) && x.locales.every((l: any) => typeof l === 'string')))
  );
}

Prevention

When it happens

Trigger: Domain entry missing `domain` or `defaultLocale`; setting `domain: 'https://example.fr:3000'` (contains `:`); two domain entries both declaring `defaultLocale: 'fr'`; `locales: [123]` inside a domain; a locale string listed under two different domains.

Common situations: Copying a domain URL with the protocol/port prefix; reusing the same default locale across regional domains by mistake; mixing locale arrays across domains during a migration; silent `console.warn` for duplicate locales that then escalate to a throw.

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/78f9a2a2740795bd. Report an issue: GitHub.