Next.js Change
In a Pages Router app that combines a basePath, next.config.js rewrites, middleware, and a catch-all dynamic route, a client-side Link navigation could land on the page with a corrupted router.query. Instead of holding the captured route segments, the catch-all param ended up holding the segments of the internal _next/data/<buildId>/... URL. Page code that built further URLs from router.query then produced malformed paths and 404s in production.
The corruption originated in getMiddlewareData, which was passing nextConfig: undefined to getNextPathnameInfo whenever __NEXT_HAS_REWRITES was true. With basePath set, the basePath prefix was never stripped from the data-source URL, so the /_next/data/ check never matched and the data prefix was left intact. The resulting pathname flowed back into routeInfo.resolvedAs, and on catch-all routes the route regex matched it and overwrote router.query.<param> with the data-URL segments.
The original ternary at this call site (#48753) was there to avoid stripping the locale prefix, since resolveRewrites needs as to keep its locale prefix to match locale-aware rewrite sources. Disabling all of nextConfig was too coarse and broke basePath stripping. The fix passes a partial config with only basePath and trailingSlash, keeping the locale prefix intact while still letting getNextPathnameInfo strip basePath before the data-prefix check.
Commit: f849930
PR: #93294
What Changed
packages/next/src/shared/lib/router/router.ts: in getMiddlewareData, when __NEXT_HAS_REWRITES is true, pass { basePath, trailingSlash } to getNextPathnameInfo instead of undefined. This keeps locale-prefix handling intact for resolveRewrites while still letting basePath be stripped before the /_next/data/ data-prefix check.
- New regression test
test/e2e/middleware-dynamic-basepath-matcher-rewrites/: catch-all + basePath + rewrites + middleware. Asserts router.query.path is preserved across a client-side <Link> navigation.
Impact on vinext
vinext implements Pages Router client-side navigation, including middleware data fetching and catch-all route param extraction. If vinext's client router has the same handling — or if it derives resolvedAs through equivalent code — the same corruption can occur when an app combines:
basePath configured
next.config.js rewrites (any of beforeFiles/afterFiles/fallback)
- Middleware
- A catch-all dynamic page (
pages/[...path].tsx)
Symptom: after a client-side <Link> navigation, router.query.<catchAll> contains ["_next", "data", "<buildId>", ...] instead of the actual captured segments. Pages that build further URLs from router.query produce malformed paths and 404s.
Suggested Action
- Audit vinext's Pages Router client navigation path for the equivalent of
getMiddlewareData → getNextPathnameInfo flow. Confirm whether it strips basePath from the data-source URL before checking for the /_next/data/ prefix when rewrites are present.
- If the same ternary pattern exists (skip nextConfig when rewrites are enabled), narrow it to skip only locale handling — pass
{ basePath, trailingSlash } so basePath stripping still runs.
- Port the regression test from
test/e2e/middleware-dynamic-basepath-matcher-rewrites/ into vinext's Pages Router fixtures: catch-all page, basePath, middleware, rewrites, client-side <Link> navigation, assert router.query.path matches the captured segments (not the _next/data ones).
- Include reference comment in the ported test pointing back to the Next.js test file.
Next.js Change
In a Pages Router app that combines a
basePath,next.config.jsrewrites, middleware, and a catch-all dynamic route, a client-sideLinknavigation could land on the page with a corruptedrouter.query. Instead of holding the captured route segments, the catch-all param ended up holding the segments of the internal_next/data/<buildId>/...URL. Page code that built further URLs fromrouter.querythen produced malformed paths and 404s in production.The corruption originated in
getMiddlewareData, which was passingnextConfig: undefinedtogetNextPathnameInfowhenever__NEXT_HAS_REWRITESwas true. WithbasePathset, the basePath prefix was never stripped from the data-source URL, so the/_next/data/check never matched and the data prefix was left intact. The resulting pathname flowed back intorouteInfo.resolvedAs, and on catch-all routes the route regex matched it and overwroterouter.query.<param>with the data-URL segments.The original ternary at this call site (#48753) was there to avoid stripping the locale prefix, since
resolveRewritesneedsasto keep its locale prefix to match locale-aware rewrite sources. Disabling all ofnextConfigwas too coarse and brokebasePathstripping. The fix passes a partial config with onlybasePathandtrailingSlash, keeping the locale prefix intact while still lettinggetNextPathnameInfostripbasePathbefore the data-prefix check.Commit:
f849930PR: #93294
What Changed
packages/next/src/shared/lib/router/router.ts: ingetMiddlewareData, when__NEXT_HAS_REWRITESis true, pass{ basePath, trailingSlash }togetNextPathnameInfoinstead ofundefined. This keeps locale-prefix handling intact forresolveRewriteswhile still letting basePath be stripped before the/_next/data/data-prefix check.test/e2e/middleware-dynamic-basepath-matcher-rewrites/: catch-all + basePath + rewrites + middleware. Assertsrouter.query.pathis preserved across a client-side<Link>navigation.Impact on vinext
vinext implements Pages Router client-side navigation, including middleware data fetching and catch-all route param extraction. If vinext's client router has the same handling — or if it derives
resolvedAsthrough equivalent code — the same corruption can occur when an app combines:basePathconfigurednext.config.jsrewrites(any of beforeFiles/afterFiles/fallback)pages/[...path].tsx)Symptom: after a client-side
<Link>navigation,router.query.<catchAll>contains["_next", "data", "<buildId>", ...]instead of the actual captured segments. Pages that build further URLs fromrouter.queryproduce malformed paths and 404s.Suggested Action
getMiddlewareData→getNextPathnameInfoflow. Confirm whether it stripsbasePathfrom the data-source URL before checking for the/_next/data/prefix when rewrites are present.{ basePath, trailingSlash }so basePath stripping still runs.test/e2e/middleware-dynamic-basepath-matcher-rewrites/into vinext's Pages Router fixtures: catch-all page, basePath, middleware, rewrites, client-side<Link>navigation, assertrouter.query.pathmatches the captured segments (not the_next/dataones).