#99357·Next.js

`next/link` prefetches across routers in mixed App/Pages apps (`_next/data` 404 for App routes, HTML prefetch for Pages routes)

Author: KnotzerIOCreated Sep 28, 2026Updated Sep 28, 2026
LabelsLinking and NavigatingPages Router

Link to the code that reproduces this issue

https://github.com/KnotzerIO/next-cross-router-prefetch-repro

Deployed: https://next-cross-router-prefetch-repro.vercel.app

To Reproduce

  1. npm i && npm run build && npm start (production build, prefetching is disabled in next dev)
  2. Open http://localhost:3456/about with the Network tab open, filter for _next/data
  3. Scroll the /dashboard link into view, then hover it
  4. Open http://localhost:3456/dashboard, filter for _rsc
  5. Optional: npx playwright install chromium && node scripts/record.mjs prints every request as a table

The app has one App Router page (app/dashboard) and two Pages Router routes: pages/about.js (SSG) and an SSG optional catch-all pages/[[...slug]].js. No next.config.

Current vs. Expected behavior

A) Pages Router link to an App Router route

On /about, <Link href="/dashboard"> requests /_next/data/<buildId>/dashboard.json?slug=dashboard on viewport prefetch and again on hover. It returns 404. The click itself works, the client router filter hard navigates to the App route.

Network tab on /about: two 404s for dashboard.json

Expected: no data request for a path the client router filter already identified as an App Router route.

In prefetch() the filter match only stores a marker and the function keeps going:

https://github.com/vercel/next.js/blob/e32cb8e424ad0b0d0557a9cb17fbeb261bd13fa8/packages/next/src/shared/lib/router/router.ts#L2637-L2661

typescript
if (await this._bfl(asPath, resolvedAs, options.locale, true)) {
  // stores { __appRouter: true }
}
await Promise.all([
  this.pageLoader._isSsg(route).then((isSsg) => isSsg ? fetchNextData({ ... }) : false),
  ...
])

route resolves to the SSG catch-all, so fetchNextData runs. Any SSG Pages route with a dynamic segment that matches the path triggers it (pages/[slug].js too, fallback: false makes no difference). prefetch={false} does not help: on the Pages Router it only disables the viewport prefetch and hover still calls prefetch(), as documented. With the early return that hover prefetch would also stop hitting _next/data:

https://github.com/vercel/next.js/blob/e32cb8e424ad0b0d0557a9cb17fbeb261bd13fa8/packages/next/src/client/link.tsx#L634-L657

This looks like a missing early return after the filter match. change() hard navigates on every filter match anyway, so nothing fetched after the match is ever used. The fixes for #98180 and #98181 (#98182, #98187) touched the same block, but not this path.

B) App Router link to a Pages Router route

On /dashboard, <Link href="/about"> sends a viewport prefetch GET /about?_rsc=... with RSC: 1 and Next-Router-Prefetch: 1. The Pages route answers with the full HTML document, which is thrown away because it is not a Flight response.

Network tab on /dashboard: RSC prefetch of /about

Response of the RSC prefetch is the HTML document

Expected, per the migration guide:

When navigating between routes served by the different Next.js routers, there will be a hard navigation. Automatic link prefetching with next/link will not prefetch across routers.

So either no automatic prefetch for a Pages route, or the docs should say that it happens.

On Vercel the same requests are sent, but they come back as a 2 byte application/octet-stream body instead of the HTML document. With next start every prefetch downloads the whole page.

Related: on click the App Router sends one more RSC request, gets HTML again and falls back to a document navigation. This also happens with prefetch={false} (/dashboard/no-prefetch in the repo). The docs do not cover this case, but it adds a round trip to every App to Pages navigation:

https://github.com/vercel/next.js/blob/e32cb8e424ad0b0d0557a9cb17fbeb261bd13fa8/packages/next/src/client/components/router-reducer/fetch-server-response.ts#L216-L234

Recorded requests (16.3.6, next start)

Start page Step Request Headers Status Content-Type
/about load /_next/data/<id>/dashboard.json?slug=dashboard prefetch 404
/about hover /_next/data/<id>/dashboard.json?slug=dashboard prefetch 404
/about click document /dashboard 200 text/html
/dashboard load /about?_rsc=j79V... RSC: 1, prefetch 200 text/html
/dashboard click /about?_rsc=T1N9... RSC: 1 200 text/html
/dashboard click document /about 200 text/html
/dashboard/no-prefetch click /about?_rsc=zTtM... RSC: 1 200 text/html
/dashboard/no-prefetch click document /about 200 text/html

Same result on 16.4.0-canary.51. A is identical on the Vercel deployment.

Provide environment information

bash
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.6.0
  Available memory (MB): 49152
  Available CPU cores: 12
Binaries:
  Node: 24.20.0
  npm: 11.19.0
Relevant Packages:
  next: 16.3.6
  react: 19.2.0
  react-dom: 19.2.0
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Linking and Navigating, Pages Router

Which stage(s) are affected? (Select all that apply)

next build (local), next start (local), Vercel (Deployed), Other (Deployed)

Additional context

We run a large Pages Router app and are migrating section by section to the App Router, as the migration guide suggests. In production this shows up as a 404 in the network log for every Pages page that links into the App section, and as full HTML documents downloaded for every App page that links back.