Skip to content

Add opt-in next routing resolver delegation#94102

Draft
timneutkens wants to merge 1 commit into
codex/server-routing/result-mapperfrom
codex/server-routing/opt-in-delegation
Draft

Add opt-in next routing resolver delegation#94102
timneutkens wants to merge 1 commit into
codex/server-routing/result-mapperfrom
codex/server-routing/opt-in-delegation

Conversation

@timneutkens

@timneutkens timneutkens commented May 25, 2026

Copy link
Copy Markdown
Contributor

Stack

Depends on #94101 (codex/server-routing/result-mapper). This is the top PR in the server-routing stack.

What?

Adds an opt-in live server delegation path from resolve-routes.ts to @next/routing behind __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1. The hook is in the actual next start route resolver used by router-server.ts, but default behavior remains unchanged unless the env var is enabled.

This also adds @next/routing as a next package dependency, ensures isolated tests pack the local @next/routing package, and fixes repeated destination query parameters in @next/routing so rewrites like /?items=1&items=2 preserve array values.

Why?

This is the first live-server adoption step after the audit, adapter input builder, and result mapper PRs. It keeps compatibility first: @next/routing is used only for selected production route-resolution paths where the mapped result can be returned safely, otherwise the legacy resolver continues.

Expanded e2e coverage showed that broader custom-route manifests still need live-server semantics for has/missing, first-segment params such as /:path, catch-all params, destination query interpolation, and external redirect query encoding. This PR now detects those unsupported features and keeps those apps on the legacy resolver until the adapter models them exactly.

How?

  • Build @next/routing state from the existing fsChecker adapter.
  • Convert Node request headers and URL state into the package resolver input.
  • Map resolved package results back into the live resolveRoutes result shape.
  • Return delegated results only when the package resolver actually finished or matched an output; otherwise continue through the legacy resolver.
  • Keep dynamic Pages Router matching on the legacy resolver for now by omitting dynamic route templates and dynamic _next/data pathnames from the live opt-in @next/routing input.
  • Skip live opt-in delegation for custom-route manifests with unsupported features: has/missing, catch-all source params, first-segment source params, query interpolation in destinations, or external destinations with query strings.
  • Still allow compatible custom routes in apps that contain dynamic routes to delegate to @next/routing.

Verification

  • pnpm --filter @next/routing test
  • pnpm testonly test/unit/server-routing-equivalence/next-routing-adapter.test.ts test/unit/server-routing-equivalence/resolve-routes.test.ts
  • pnpm --filter @next/routing build
  • pnpm --filter=next build
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/rewrites-has-condition/rewrites-has-condition.test.ts test/e2e/rewrites-destination-query-array/rewrites-destination-query-array.test.ts test/e2e/dynamic-routing/dynamic-routing.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/custom-routes/custom-routes.test.ts test/e2e/custom-routes-catchall/custom-routes-catchall.test.ts test/e2e/rewrites-client-resolving/rewrites-client-resolving.test.ts test/e2e/invalid-static-asset-404-pages/invalid-static-asset-404-pages.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/basepath/redirect-and-rewrite.test.ts test/e2e/trailing-slashes-rewrite/trailing-slashes-rewrite.test.ts test/e2e/i18n-data-route/i18n-data-route.test.ts test/e2e/custom-routes-i18n/custom-routes-i18n.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/e2e/trailingslash-with-rewrite/index.test.ts test/e2e/i18n-support-index-rewrite/i18n-support-index-rewrite.test.ts test/e2e/app-dir/rsc-query-routing/rsc-query-routing.test.ts test/e2e/middleware-rewrites/test/index.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/production/custom-server/custom-server.test.ts test/production/required-server-files-ssr-404/test/index.test.ts test/production/static-404/static-404.test.ts test/production/middleware-prefetch/middleware-prefetch.test.ts test/production/rewrite-request-smuggling/rewrite-request-smuggling.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-turbo test/production/rewrite-request-smuggling/rewrite-request-smuggling.test.ts test/production/standalone-mode/required-server-files/required-server-files.test.ts -t "does not smuggle|should resolve correctly when a redirect is returned|should handle data routes with optional catch-all params|should de-dupe HTML/data requests"
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/development/gssp-redirect-with-rewrites/gssp-redirect-with-rewrites.test.ts test/development/dynamic-route-rename/dynamic-route-rename.test.ts test/development/trailing-slash-dist/trailing-slash-dist.test.ts
  • __NEXT_EXPERIMENTAL_USE_NEXT_ROUTING_RESOLVER=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/development/middleware-errors/index.test.ts
  • git diff --check

Note: a combined dev bucket including middleware-errors, middleware-dev-update, dynamic-route-rename, gssp-redirect-with-rewrites, and trailing-slash-dist had one middleware-errors failure, but middleware-errors passed when isolated. The opt-in resolver is disabled in dev, so this looks like a dev error-suite interaction rather than an opt-in routing regression.

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Failing test suites

Commit: 59a81ff | About building and testing Next.js

pnpm test-dev test/development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts (job)

  • hmr-intercept-routes > should update intercept routes via HMR (DD)
Expand output

● hmr-intercept-routes › should update intercept routes via HMR

page.waitForSelector: Timeout 10000ms exceeded.
Call log:
  - waiting for locator('#default-intercept') to be visible

  560 |
  561 |     return this.startChain(async () => {
> 562 |       const el = await page.waitForSelector(selector, {
      |                             ^
  563 |         timeout,
  564 |         state,
  565 |       })

  at waitForSelector (lib/browsers/playwright.ts:562:29)
  at Playwright._chain (lib/browsers/playwright.ts:692:23)
  at Playwright._chain [as startChain] (lib/browsers/playwright.ts:673:17)
  at Playwright.startChain [as waitForElementByCss] (lib/browsers/playwright.ts:561:17)
  at Object.waitForElementByCss (development/app-dir/hmr-intercept-routes/hmr-intercept-routes.test.ts:38:21)

pnpm test-start test/production/app-dir/metadata-static-route-cache/metadata-static-route-cache.test.ts (job)

  • app dir - metadata static routes cache > should generate different content after replace the static metadata file (DD)
Expand output

● app dir - metadata static routes cache › should generate different content after replace the static metadata file

next already started

  65 |   ) {
  66 |     if (this.childProcess) {
> 67 |       throw new Error('next already started')
     |             ^
  68 |     }
  69 |
  70 |     this._cliOutput = ''

  at NextStartInstance.start (lib/next-modes/next-start.ts:67:13)
  at Object.start (production/app-dir/metadata-static-route-cache/metadata-static-route-cache.test.ts:17:16)

@timneutkens
timneutkens force-pushed the codex/server-routing/opt-in-delegation branch from 6b71457 to 19ecc5b Compare May 25, 2026 16:33

@vercel vercel Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Additional Suggestion:

Inline snapshot for next-server.js.nft.json is missing two new trace entries (@next/routing and @vercel/routing-utils/superstatic.js) added by the @next/routing dependency.

Fix on Vercel

@timneutkens
timneutkens force-pushed the codex/server-routing/opt-in-delegation branch 2 times, most recently from 7656147 to efb45e1 Compare May 27, 2026 09:51
@timneutkens
timneutkens force-pushed the codex/server-routing/result-mapper branch from 6b693f0 to 278165d Compare May 27, 2026 10:12
@timneutkens
timneutkens force-pushed the codex/server-routing/opt-in-delegation branch from efb45e1 to 147d80e Compare May 27, 2026 10:12
@github-actions

github-actions Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

Stats cancelled

Commit: 59a81ff
View workflow run

@timneutkens
timneutkens force-pushed the codex/server-routing/result-mapper branch from eeebd58 to 50aa72b Compare May 27, 2026 12:40
@timneutkens
timneutkens force-pushed the codex/server-routing/opt-in-delegation branch from f8f18f7 to 9270567 Compare May 27, 2026 12:40
@timneutkens
timneutkens force-pushed the codex/server-routing/result-mapper branch from 50aa72b to 8c11b31 Compare June 4, 2026 11:19
@timneutkens
timneutkens force-pushed the codex/server-routing/opt-in-delegation branch from 9270567 to 59a81ff Compare June 4, 2026 11:20
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.

1 participant