vercel / vercel/next.js

Segment prefetch requests another link's segment path behind a proxy rewrite, causing 404s

Open
#97,244 1 comment 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Linking and Navigating Middleware
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
  1. Open http://localhost:3000/ with DevTools → Network open, filtered on _rsc.
  2. The home page renders one link to /en — already prefixed, so proxy.js passes it
    through untouched, and its target has the home route shape — and six links to
    /alpha/zeta, which proxy.js rewrites to /de/<slug> and whose targets have
    the catch-all route shape.
  3. Only the first four links get a /_tree request. The three links after that reuse the
    segment path of /en and request it at their own URL, which returns 404.

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 in client/components/links.ts.
  • #95315 — "Fix metadata title dropped on
    soft navigation with Cache Components", changing
    client/components/segment-cache/cache.ts and .../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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.