next.js
f81c34f8 - Hard-navigate to app routes shadowed by a pages dynamic route (#95185)

Commit
81 days ago
Hard-navigate to app routes shadowed by a pages dynamic route (#95185) When an App Router route begins with a dynamic segment (for example `app/[locale]/about`), a client-side navigation from a Pages Router page could render the wrong route. The Pages Router resolves the destination against its own routes only, so `/en/about` would match a less specific Pages Router dynamic route such as `pages/[locale]/[category]` instead of the App Router page. The server pools app and pages routes and ranks them by specificity, so a hard reload always rendered the correct App Router page; only the client-side soft navigation diverged. The client router filter is responsible for detecting destinations owned by the other router and forcing a hard navigation, but `createClientRouterFilter` only recorded the static prefix of a dynamic route. App routes whose first segment is dynamic have no static prefix, so they contributed nothing to the filter and the Pages Router never learned to hand them off. The filter now also stores a normalized pattern for those routes, with dynamic segments replaced by a placeholder token (`/[locale]/about` becomes `/[]/about`). After the Pages Router resolves a navigation to a dynamic route, `hasDynamicFilterCandidate` reconstructs the candidate app-route patterns from that route and the concrete path and triggers a hard navigation when any candidate is present in the dynamic filter. This also covers catch-all and optional catch-all pages routes, whose final parameter absorbs a variable number of segments. The shared placeholder token and the client-side check live in the new `dynamic-filter-pattern.ts`, while the build-only encoder stays in `create-client-router-filter.ts`. The change only ever turns a soft navigation into a hard navigation, and only when an app route is genuinely more specific than the resolved pages route, so a structural match always agrees with the server's resolution. Apps without dynamic app routes are unaffected because the dynamic filter stays empty. The `pages-to-app-routing` end-to-end suite is restructured around a `fixtures/` directory and gains coverage for the dynamic-segment shadowing, the catch-all and optional catch-all variants, `basePath`, middleware, and a guard that legitimate Pages Router dynamic routes still navigate client-side. The first request to a not-yet-compiled dynamic route can return a transient 404 in development, a pre-existing source of CI flakiness, so the tests now warm the route up with a direct request before driving the browser. fixes #74696
Author
Parents
Loading