Skip to content

Pages Router: preserve catch-all router.query across client navigation with basePath + rewrites #1196

@github-actions

Description

@github-actions

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:

  1. basePath configured
  2. next.config.js rewrites (any of beforeFiles/afterFiles/fallback)
  3. Middleware
  4. 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 getMiddlewareDatagetNextPathnameInfo 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    nextjs-trackingTracking issue for a Next.js canary change relevant to vinext

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions