Skip to content

Resolve App Router routes when Pages Router i18n domains are configured#94606

Open
RobVermeer wants to merge 1 commit into
vercel:canaryfrom
RobVermeer:fix/i18n-domain-app-pages-86048
Open

Resolve App Router routes when Pages Router i18n domains are configured#94606
RobVermeer wants to merge 1 commit into
vercel:canaryfrom
RobVermeer:fix/i18n-domain-app-pages-86048

Conversation

@RobVermeer

@RobVermeer RobVermeer commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What?

App Router routes that use a dynamic locale segment (e.g. app/[lang]/test) returned 404 — and next build failed — whenever the Pages Router i18n config (with domains) was set. This makes both routers work together under domain-based i18n.

Fixes #86048

Why?

Pages Router i18n treats the locale as metadata and strips the /<locale> prefix from a request before route matching. App Router has no i18n system: when you localize with app/[lang]/..., the locale is a real route segment that must stay in the path so [lang] can capture it.

With i18n configured, the Pages-Router stripping ran for every request and removed the prefix App Router needs, so /[lang]/test could never match /nl-NL/test (whether reached directly or via a proxy.ts rewrite of /test), and statically prerendering it failed the build with export path '/test' doesn't match the '/[lang]/test' page.

How?

The locale prefix was being stripped in three places; each now preserves it for App Router routes that actually consume it, while Pages Router behavior is unchanged:

  • server/lib/router-utils/resolve-routes.ts (dev/prod routing): dynamic App Router routes are also matched against the full, un-stripped pathname so a leading [lang] segment can capture the locale. Pages Router routes still match the locale-stripped pathname.
  • server/base-server.ts (invoke-path handling): when the resolved output is an App Router route whose regex matches the locale-prefixed path, the prefix is kept (and the locale treated as explicit) instead of being stripped.
  • export/worker.ts (static generation): App Router export paths keep their locale prefix and match params against the full path.

Every branch is gated so non-i18n apps, Pages Router routing, and App Router routes that don't model the locale (e.g. /about, /items/[id]) are unaffected.

This is an alternative approach to #86116 — it additionally fixes direct /<locale>/<route> access and static generation, which that draft documents as a known limitation.

Verification

  • New e2e fixture test/e2e/i18n-app-pages-domain passes 7/7 in all four modes (dev+webpack, start+webpack, dev+turbo, start+turbo): Pages Router root, App Router via proxy.ts rewrite, direct locale-prefixed URLs, a nested /[lang]/blog/[slug] route, and production static generation.
  • No regressions: test/e2e/i18n-support (103 passed) and test/e2e/i18n-data-route (12 passed).
  • pnpm --filter=next types, prettier, and eslint pass.

Related issues

This is the same underlying root cause behind several long-standing reports of the Pages Router i18n config breaking App Router routing (~76 👍 combined):

Possibly related but not verified here (different symptoms): #84371, #90131.

Comment thread packages/next/src/server/base-server.ts
When the Pages Router `i18n` config is set (e.g. with domain routing), the
locale prefix was stripped from every request before route matching. App
Router routes whose first segment is the locale (e.g. `app/[lang]/test`)
therefore could never match a locale-prefixed pathname such as `/nl-NL/test`
(whether reached directly or via a `proxy.ts` rewrite of `/test`), and
returned 404. Statically prerendering such routes also failed the build with
"export path doesn't match the page".

App Router routes are not locale-aware: the locale is part of the route path
itself, so the Pages Router locale machinery must not strip it for them. This
is fixed at the three points where the locale prefix was being removed:

- resolve-routes (dev/prod routing): App Router dynamic routes are now also
  matched against the full, un-stripped pathname so a leading `[lang]` segment
  can capture the locale. Pages Router routes still match the stripped path.
- base-server (invoke path handling): when the resolved output is an App
  Router route that actually consumes the locale prefix, the locale prefix is
  kept in the pathname (the basePath is still stripped) and the locale is
  treated as explicit, instead of being stripped like a Pages Router route.
- export worker (static generation): App Router export paths keep their locale
  prefix and match params against the full path.

All changes are gated so non-i18n apps and Pages Router behavior are
unaffected. Adds e2e fixtures covering both routers under domain-based i18n
across direct and proxy-rewritten access, with and without a basePath.

Fixes vercel#86048
@RobVermeer RobVermeer force-pushed the fix/i18n-domain-app-pages-86048 branch from debebbf to 41c1891 Compare June 9, 2026 16:01
@RobVermeer RobVermeer marked this pull request as ready for review June 9, 2026 16:06
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.

App Router routes return 404 when using Pages Router i18n with domain routing

1 participant