Skip to content

feat: Add per-domain localePrefix override support#2273

Merged
amannn merged 14 commits into
amannn:mainfrom
frankmatheron:feat/per-domain-prefix-mode
Apr 28, 2026
Merged

feat: Add per-domain localePrefix override support#2273
amannn merged 14 commits into
amannn:mainfrom
frankmatheron:feat/per-domain-prefix-mode

Conversation

@frankmatheron

@frankmatheron frankmatheron commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an optional localePrefix property to DomainConfig, allowing each domain to override the global localePrefix mode independently. This eliminates the need for separate builds per domain when different domains require different prefix behaviors.

Closes #2272.

Motivation

When using domain-based routing, all domains currently share the same localePrefix mode. This is limiting for setups like:

  • A single-locale domain (us.example.com) that should never show prefixes
  • A multi-locale domain (ca.example.com) that should always show prefixes for clarity

The previous workaround was building the app separately per domain with different env-injected configs — costly and complex.

Usage

export const routing = defineRouting({
  locales: ['en-US', 'en-CA', 'fr-CA'],
  defaultLocale: 'en-US',
  domains: [
    {
      domain: 'us.example.com',
      defaultLocale: 'en-US',
      locales: ['en-US'],
      localePrefix: 'never'       // No prefix on this domain
    },
    {
      domain: 'ca.example.com',
      defaultLocale: 'en-CA',
      locales: ['en-CA', 'fr-CA'],
      localePrefix: 'always'      // Always show prefix
    }
  ],
  localePrefix: 'as-needed'       // Global default for domains without override
});

When localePrefix is set on a domain, it completely replaces the global mode for that domain. Domains without the property continue using the global setting. Custom prefixes (e.g. /spain for es) remain global and are respected regardless of the domain-level mode.

Implementation

The change touches 4 source files, all following the same pattern — resolve the effective mode via domain?.localePrefix || globalMode:

  • routing/types.tsx — Adds localePrefix?: LocalePrefixMode to DomainConfig. Uses the simple string union rather than the verbose config object, since domains only override the mode, not the prefix mapping.
  • middleware/middleware.tsx — Introduces effectiveLocalePrefixMode that checks the matched domain's override before falling back to the global mode. Used in 4 places: unprefixed routing check, never mode redirect, alternate links guard, and cross-domain redirect.
  • middleware/getAlternateLinksHeaderValue.tsx — Respects per-domain mode when deciding whether to prefix alternate link URLs.
  • navigation/shared/utils.tsxapplyPathnamePrefix now looks up the domain for the given locale and uses its localePrefix override when generating pathnames (affects Link, getPathname, redirect).

Tests

Unit tests (26 new tests):

  • middleware.test.tsx — 14 tests: 3 global modes × domain overrides (never, always, as-needed), plus fallback to global when no override is set
  • getAlternateLinksHeaderValue.test.tsx — 3 tests: cross-domain alternate link generation with mixed modes
  • createNavigation.test.tsx — 9 tests: getPathname with domain overrides for all 3 global modes

E2E tests (29 new tests):

  • 3 domains (never.example.com, always.example.com, as-needed.example.com) each tested for: routing, redirects, query string preservation, Link href generation, usePathname, alternate links headers, and localized pathnames
  • Cross-domain navigation test
  • Unsupported locale fallback test

Bundle size impact

createNavigation grows by ~20B (minified + brotli) due to the domain lookup in applyPathnamePrefix. Size limits bumped accordingly:

  • react-client: 2.345 → 2.375 KB
  • react-server: 3.095 → 3.115 KB

Docs

  • Added inline example in the domains section of configuration.mdx
  • Updated the "Can I use a different localePrefix per domain?" FAQ from "not supported" to documenting the new feature with examples

@vercel

vercel Bot commented Feb 24, 2026

Copy link
Copy Markdown

@frankmatheron is attempting to deploy a commit to the next-intl Team on Vercel.

A member of the Team first needs to authorize it.

@frankmatheron frankmatheron force-pushed the feat/per-domain-prefix-mode branch from c0a144e to ac54cbf Compare February 25, 2026 10:49
Adds optional localePrefix property to DomainConfig type, allowing
domains to override the global localePrefix mode. This enables
different URL prefix strategies per domain (e.g., always prefix on
one domain, never prefix on another).

- Add localePrefix property to DomainConfig type
- Update applyPathnamePrefix to check domain override first
- Add comprehensive tests for all mode combinations
- Update documentation with examples
@frankmatheron frankmatheron force-pushed the feat/per-domain-prefix-mode branch from ac54cbf to 5599aac Compare February 25, 2026 10:58
Updates middleware to respect per-domain localePrefix overrides:
- Check domain.localePrefix before falling back to global mode
- Apply effective mode in routing decisions and redirects
- Update alternate links generation to use domain-specific mode
@frankmatheron frankmatheron force-pushed the feat/per-domain-prefix-mode branch from 5599aac to 1ae8398 Compare February 25, 2026 10:59
frankmatheron and others added 11 commits February 25, 2026 12:13
Adds comprehensive test coverage for domain-specific localePrefix overrides:
- Tests for global 'as-needed' with domain overrides ('never', 'always')
- Tests for global 'always' with domain overrides ('never', 'as-needed')
- Tests for global 'never' with domain overrides ('always', 'as-needed')
- Verifies correct redirect and serve behavior for each combination
Adds test coverage for alternate links generation with domain-specific
localePrefix overrides. Tests verify correct hreflang link generation
for all combinations of global and domain-level modes.
feat: add Dutch locale and improve always domain test coverage
Consolidate usePathname tests (6→3), fix never-mode alternate
links assertions, add query string preservation and unsupported
locale tests, share browser instances across describe blocks.
@vercel

vercel Bot commented Apr 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
next-intl-docs Ready Ready Preview, Comment Apr 28, 2026 2:52pm
next-intl-example-app-router Ready Ready Preview, Comment Apr 28, 2026 2:52pm
next-intl-example-app-router-without-i18n-routing Ready Ready Preview, Comment Apr 28, 2026 2:52pm

Request Review

@amannn amannn mentioned this pull request Apr 28, 2026

@amannn amannn left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @frankmatheron, thanks a lot for exploring this so carefully and the extensive & thoughtful PR!

Looking through all the changes, it looks like this works really well now—so green light from me to release this.

I've added a commit with some nits, mainly around docs. Hope that looks good to you.

chromium,
expect,
test as it
} from '@playwright/test';

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good, thanks for making the test suite so extensive!

I'm planning to refactor the e2e test strategy a bit after this PR is merged (see #2310).

I really appreciate how clean your proposed changes are here, any chance you'd be interested in helping with that? Only if you're up for it, otherwise I can look into it.

@amannn amannn Apr 29, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The e2e refactoring seems quite important for upcoming features, I'll give this a go.

Edit: At least extractor tests have been pulled into /e2e now, tests from the playground are still to be done …

@amannn amannn changed the title feat: add per-domain localePrefix override support feat: Add per-domain localePrefix override support Apr 28, 2026
@amannn amannn merged commit 3e9febf into amannn:main Apr 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-domain localePrefix override

2 participants