Segment prefetch requests another link's segment path behind a proxy rewrite, causing 404s
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 142k
- Forks
- 32.5k
- Avg merge
- 2d 14h
- Merged PRs (30d)
- 351
Description
Link to the code that reproduces this issue
https://github.com/okadriu/next-16-3-segment-prefetch-404
To Reproduce
pnpm install
pnpm build
pnpm start
- Open http://localhost:3000/ with DevTools → Network open, filtered on
_rsc. - The home page renders one link to
/en— already prefixed, soproxy.jspasses it
through untouched, and its target has the home route shape — and six links to
/alpha…/zeta, whichproxy.jsrewrites to/de/<slug>and whose targets have
the catch-all route shape. - Only the first four links get a
/_treerequest. The three links after that reuse the
segment path of/enand request it at their own URL, which returns404.
pnpm verify automates steps 1–3 and exits non-zero when a prefetch fails.
The whole app is four files:
app/[locale]/layout.js root layout
app/[locale]/page.js home → segment /$d$locale/__PAGE__
app/[locale]/[...slug]/page.js catch-all → segment /$d$locale/$c$slug/__PAGE__
proxy.js rewrites /<slug> → /de/<slug>
Current vs. Expected behavior
Current. The last three prefetches ask for the home page segment at a catch-all URL:
200 /en next-router-segment-prefetch: /$d$locale/__PAGE__
200 /alpha next-router-segment-prefetch: /$d$locale/$c$slug/__PAGE__
200 /beta next-router-segment-prefetch: /$d$locale/$c$slug/__PAGE__
200 /gamma next-router-segment-prefetch: /$d$locale/$c$slug/__PAGE__
404 /delta next-router-segment-prefetch: /$d$locale/__PAGE__ ← home segment
404 /epsilon next-router-segment-prefetch: /$d$locale/__PAGE__ ← home segment
404 /zeta next-router-segment-prefetch: /$d$locale/__PAGE__ ← home segment
Everything else about those responses is correct — the rewrite resolved, the route is
prerendered, the cache hit:
x-nextjs-rewritten-path: /de/delta
x-nextjs-prerender: 1
x-nextjs-cache: HIT
→ 404
/$d$locale/__PAGE__ genuinely does not exist at /delta, so 404 is the correct answer
to the question that was asked. The request is what is wrong.
Expected. Each prefetch requests the segment path of the route its own href points at,
so all seven links return 200 — as they do on 16.2.12.
Impact. Navigation still works, because a failed prefetch falls back to a full
navigation. But every affected link logs Failed to load resource: the server responded with a status of 404, wastes a request, and fails any end-to-end test that asserts a clean
browser console.
Both ingredients are required. Bisected by adding one at a time:
| route group | rewrite + unprefixed hrefs | link to a differently shaped route | failing prefetches |
|---|---|---|---|
| – | – | – | 0 |
| – | yes | – | 0 |
| yes | yes | – | 0 |
| yes | yes | yes (/en) |
3 |
| – | – | yes (/en) |
0 |
| yes | – | yes (/en) |
0 |
| – | yes | yes (/en) |
3 |
| – | yes | yes (/, also rewritten) |
0 |
The rewrite is required, and so is a link whose target is not rewritten and has a
different route shape. Route groups and parallel routes are not required — they only
change the segment string and how many links are affected (a parallel slot doubles it,
because each link is prefetched once per slot).
cacheComponents and partialPrefetching are not enabled.
Workaround. prefetch={false} on the single differently shaped link removes every
failing request, and the remaining links still prefetch and resolve their own segment path:
-<Link href="/en">en</Link>
+<Link href="/en" prefetch={false}>en</Link>
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.5.0
Available memory (MB): 24576
Available CPU cores: 14
Binaries:
Node: 24.15.0
npm: 11.12.1
Yarn: 1.22.22
pnpm: 11.21.0
Relevant Packages:
next: 16.3.0
eslint-config-next: N/A
react: 19.2.7
react-dom: 19.2.7
typescript: N/A
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Linking and Navigating, Middleware
Which stage(s) are affected? (Select all that apply)
next start (local)
Additional context
I bisected the canary releases between 16.2.12 and 16.3.0 with the linked reproduction:
| next | failing prefetches |
|---|---|
| 16.2.12 | 0 |
| 16.3.0-canary.75 | 0 |
| 16.3.0-canary.76 | 3 |
| 16.3.0 | 3 |
| 16.3.1-canary.13 | 3 |
So the first release that shows it is 16.3.0-canary.76, and it still reproduces on
16.3.1-canary.13, the current canary tag — which already contains
#97128 ("Fix: Optimistic routing bugs
leading to repeated prefetch loops"), so that fix does not cover this case.
The
canary.75...canary.76
range holds 16 commits, two of which touch the client-side prefetch path:
- #95393 — "Prefetch links nearest the top
of the document first", changing prefetch ordering inclient/components/links.ts. - #95315 — "Fix metadata title dropped on
soft navigation with Cache Components", changing
client/components/segment-cache/cache.tsand.../segment-cache/scheduler.ts.
The failure is order-dependent — the links that fail are the ones that never get a /_tree
request — which fits either. I have not verified which one is responsible.
On 16.2.12 every link gets /_tree first and then walks /_head, /_index,
/$d$locale, /$d$locale/$c$slug, /$d$locale/$c$slug/__PAGE__ — all 200.
Possibly related: #96965. That issue
describes a 4-request bandwidth cap in segment-cache/scheduler.js (hasNetworkBandwidth())
where a starved prefetch task resolves against the wrong route entry. The "first four links
succeed" boundary here matches that cap exactly, and in both cases a task ends up resolving
against an entry that does not belong to its own URL. The differences: #96965 needs
cacheComponents + partialPrefetching, while this reproduction has neither, and there the
starved prefetch is silently dropped, whereas here it is issued with another link's segment
path and returns 404. I do not know whether it is the same root cause.
Only tested with next start locally; I have not deployed the reproduction.
Found in a production app that uses an i18n middleware with an unprefixed default locale,
where header and footer links point at the unprefixed path and the middleware rewrites them
to the locale-prefixed route. The reproduction hand-rolls that rewrite in ~15 lines, so no
i18n library is involved.
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
Run the linked reproduction with pnpm verify and inspect the four app files named in the issue. Then read client/components/links.ts and the segment-cache files changed in the canary.75–canary.76 range, comparing prefetch ordering and route entries. Done means all seven links request their own segment path, return 200, and produce no console errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nextjs, react
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100