next.js
4731e968 - Pages Router: do not return the `__appRouter` prefetch marker as route info on shallow navigation (#98187)

Commit
4 days ago
Pages Router: do not return the `__appRouter` prefetch marker as route info on shallow navigation (#98187) ### What? `Router.prefetch()` in the Pages Router evaluates the client router filter (`_bfl`) against the `as` path, but stored the resulting `{ __appRouter: true }` marker in `router.components` under the `href` pathname. `Router.change()` looked the marker up with the `href` pathname as well. This PR keys the marker by the normalized `as` pathname and effective locale, preserves existing cache entries, and checks for markers before and after route resolution. ### Why? `href` and `as` are the same URL for most links, so the mismatch was invisible. They differ for the "route as modal" pattern (`examples/with-route-as-modal`): `href` stays on the current route (`router.pathname` + query) and `as` shows the pretty URL. When the filter matches `as`, including a false positive, the marker could replace the cached route info of the page the user is currently on: - On a static route (`/`), the `change()` guard fired for later shallow navigation and hard-navigated. - On a dynamic route, the marker was stored under the pattern (`/players/[name]`) but looked up with the concrete path (`/players/alice`). The guard could miss it, and shallow navigation could render without page props, causing missing content or an application error. A prefetch must also preserve already-loaded route info when its key matches the current route. This can happen when the page was reached through a rewrite and its canonical URL is prefetched. Hash-only navigation renders that cached entry directly, so preserving it lets the normal client-side hash update retain the page props and update `router.asPath`. Fixes #98180 ### How? - `getAppRouterMarkerKey(router, as, locale)` parses the pathname, normalizes the trailing slash, and preserves the effective locale. It expects a path without `basePath` and does not strip that prefix again. An explicit locale prefix takes precedence over the supplied locale. - The helper returns `null` for non-local URLs, leaving their existing navigation handling unchanged. - `prefetch()` stores the marker under that key only when the corresponding cache entry is absent. It does not replace loaded route info. - `change()` checks both the normalized `as` key and the `href` route. It checks the resolved route again after config rewrites, before `getRouteInfo()` reads the cache. - Non-shallow navigations continue to consult the client router filter directly. Hash-only navigation retains its normal client-side path. The complementary change in #98650 (adopts #98187) adds defensive checks when `getRouteInfo()` reads marker entries. ### Tests The e2e suites run in production (`next start`) and deploy modes. `router.prefetch()` is a no-op in development. `test/e2e/app-dir/pages-prefetch-as-app-route` covers: - Hard navigation to an App Router destination after prefetching a link whose `href` and `as` differ. - Shallow navigation on static and dynamic Pages Router routes without a reload or lost server-provided props. - Hard navigation when the `href` route holds a marker and `as` differs, or a config rewrite resolves to a marked route. - Client-side hash navigation after an awaited prefetch of the current route's canonical URL, with preserved props and an updated `router.asPath`. `test/e2e/app-dir/pages-prefetch-as-app-route-base-path` covers an internal `/docs` route with `basePath: '/docs'`, including navigation to `/docs/docs` and an unaffected shallow update on the index page. `test/e2e/app-dir/pages-prefetch-as-app-route-i18n` covers a French-only redirect prefetch without forcing an unrelated English navigation to reload. Marker-dependent tests wait for the specific key they need, rather than any marker. Runtime behaviour was also verified against the reproduction in https://github.com/Stanzilla/next-pages-router-prefetch-bloom-filter-repro by patching the compiled `router.js`. The e2e suite was not run locally. Disclosure: this change and its description were prepared with AI assistance (Claude Code) on behalf of the author.
Author
Parents
Loading