Pages Router: shallow `getRouteInfo()` returns the `__appRouter` prefetch marker as route info and crashes `_app`
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.4k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/Stanzilla/next-pages-router-prefetch-bloom-filter-repro
To Reproduce
The marker can be planted directly, so this does not depend on the Bloom filter and reproduces in next dev as well:
- Open any pages-router page with a dynamic route in the repository, for example
http://localhost:3000/players/alice. - In the console run:
window.beforeNav = 1
window.next.router.components['/players/[name]'] = { __appRouter: true }
window.next.router.push('/players/[name]?tab=stats', '/players/alice?tab=stats', { shallow: true })
The same state is reached organically through router.prefetch() when the client router filter matches the as path of a link whose href is the current route pattern (see https://github.com/vercel/next.js/issues/98180 for the end-to-end reproduction: hover the Room 00000000-0000-4000-8000-000000000c4c link, then click a tab).
Current vs. Expected behavior
Current
The page crashes. In production the UI shows "Application error: a client-side exception has occurred" and the console logs TypeError: Cannot destructure property 'title' of 'n' as it is undefined. (_app destructures pageProps). With the default _app the error is Element type is invalid because Component is undefined. window.beforeNav stays 1, so no navigation happened.
Expected
The router must never hand { __appRouter: true } to the render path. For a shallow navigation it should either fetch the route info again or hard-navigate, but not render the marker.
Root cause
Router.getRouteInfo() in packages/next/src/shared/lib/router/router.ts returns whatever is cached for the current route on a shallow navigation without checking that it is route info:
let existingInfo: PrivateRouteInfo | undefined = this.components[route]
if (routeProps.shallow && existingInfo && this.route === route) {
return existingInfo
}
Router.prefetch() stores { __appRouter: true } in the same this.components map when the client router filter matches. The __appRouter guard in Router.change() only catches the case where the marker sits under the concrete href pathname, and _bfl is skipped for shallow navigations, so a marker that ends up under the route pattern (https://github.com/vercel/next.js/issues/98180 explains how) flows straight through getRouteInfo() into _app with no Component and no props.
The same unguarded lookup exists in the rewrite branch ("Check again the cache with the new destination").
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.6.0
Available memory (MB): 36864
Available CPU cores: 12
Binaries:
Node: 24.19.0
npm: 11.17.0
Yarn: N/A
pnpm: 11.24.0
Relevant Packages:
next: 16.4.0-canary.13
eslint-config-next: N/A
react: 19.2.8
react-dom: 19.2.8
typescript: 5.9.3
Next.js Config:
output: N/A
Also reproduced with next@16.3.3 and next@16.3.4. The relevant code is unchanged on canary (538f3f6).
Which area(s) are affected? (Select all that apply)
Linking and Navigating, Pages Router
Which stage(s) are affected? (Select all that apply)
next dev (local), next build (local), next start (local)
Additional context
A fix that makes getRouteInfo() treat an __appRouter marker as a cache miss is proposed in a linked pull request. It is independent of the fix for the marker key so that a stale marker can never crash the page even if another code path writes one.
Disclosure: the investigation, the reproduction repository and this report were prepared with AI assistance (Claude Code) on behalf of the reporter.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in packages/next/src/shared/lib/router/router.ts at Router.getRouteInfo(), then inspect the shallow-navigation lookup and the rewrite branch's cache check. Run the linked reproduction repository and its documented router commands to confirm the crash. Done means the __appRouter marker is never passed to the render path and the reproduction no longer crashes during shallow navigation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nextjs, react, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100