Prefetch requests happen more than once on Next 16
Link to the code that reproduces this issue
https://github.com/jperezr21/next-16-issue
To Reproduce
pnpm buildpnpm start- Go to
/ - See logs
git checkout next-15pnpm build --turbopackpnpm start- Go to
/ - See logs
Current vs. Expected behavior
Links should be prefetched once, but they're being prefetched more than once
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 24.6.0: Mon Aug 11 21:16:31 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T6030
Available memory (MB): 18432
Available CPU cores: 11
Binaries:
Node: 22.13.1
npm: 11.1.0
Yarn: N/A
pnpm: 10.20.0
Relevant Packages:
next: 16.0.1 // Latest available version is detected (16.0.1).
eslint-config-next: N/A
react: 19.2.0
react-dom: 19.2.0
typescript: 5.9.3
Next.js Config:
output: N/A
Which area(s) are affected? (Select all that apply)
Linking and Navigating
Which stage(s) are affected? (Select all that apply)
Vercel (Deployed), next start (local)
Additional context
Identified this issue after upgrading to next 16 and seeing the number of function invocations in Vercel more than double:
Upgraded on Oct 23, downgraded on Oct 27
Related: https://github.com/vercel/next.js/issues/85470
~~I opened a PR for another issue (seems to be similar). The fix ensures that the prefetch returns a 304 status code (this was the original issue) and it seems that with this fix the prefetch only happens once. See: https://github.com/vercel/next.js/pull/85643~~
nevermind, it had a different root cause. opened a PR
This is actually working as intended. If you inspect the network requests going to the Next.js server, they send different values for the request headers. These request different files/parts/segments from Next.js, and do not represent duplicate requests.
This is due to Next.js 16 making the Client Segment Cache the new mechanism that we use to request/store segment data for prefetching. These prefetch requests made to routes that are static or partially static will not trigger an invocation, and instead will be served out of the cache.
Well, it's inefficient and expensive. Why are 4 requests needed to prefetch a page with a single div? https://github.com/jperezr21/next-16-issue/commit/1d2fc6ea336a52be9934a36db40d5fcb0c1c8fbd#diff-ef0db79d016a2379338ad88cf190a5b9a543b378ad06dbc1a78ce69c5ce77c15
I can confirm this issue and would like to add an important detail about the severity: prefetch requests are blocking user navigation.
Additional Impact: Navigation Blocking
In our production application, we're experiencing the same duplicate prefetch behavior, but with a critical UX issue:
- Navigation is blocked when users click a link before the prefetch request completes
- Multiple prefetch requests for
/c/brands?_rsc=can take 5+ seconds to complete - If a user clicks the brands link during this time, navigation is delayed until all prefetch requests finish
- This creates a very poor user experience where the site feels unresponsive
Our Setup
- Next.js: 16.0.1
- React: 19.2.0
- Navigation: Using
next-intl's Link component wrapped in custom components - Affected Route:
/c/brands(complex server component with multiple Suspense boundaries)
Reproduction
- Build:
pnpm build - Start:
pnpm start - Navigate to home page
- Open DevTools Network tab, filter for
_rsc= - Observe multiple prefetch requests for the same route
- Click the link before prefetch completes ← Navigation is blocked here
- Notice the delay until prefetch finishes
Network Behavior
We see multiple requests like:
GET /c/brands?_rsc=abc123
GET /c/brands?_rsc=def456
GET /c/brands?_rsc=ghi789
The reason that you're seeing different values for the ?_rsc= query parameter is that request headers like Next-Router-State-Tree, Next-URL, RSC, Next-Router-Prefetch and Next-Router-Segment-Prefetch are different! These requests are loading small files, and the client deduplicates requests for the same layout shared between different links. For example, if you had 10 links to different "products" that all shared the same layout, we'd only fetch the shared layouts once, reducing the total bandwidth transferred over the network.
We'll definitely take a look at any strange navigation behaviours though!
Please take a look at @carlos-dubon's issue https://github.com/vercel/next.js/issues/85470. Upgrading caused the number of requests to triple and latency to suffer. I get that with proper use of cache components this can probably be mitigated. But you should communicate clearly that upgrading without implementing these changes has these negative effects. This is a breaking change and it was not communicated clearly.
Please take a look at @carlos-dubon's issue #85470. Upgrading caused the number of requests to triple and latency to suffer. I get that with proper use of cache components this can probably be mitigated. But you should communicate clearly that upgrading without implementing these changes has these negative effects. This is a breaking change and it was not communicated clearly.
It does not help with Cache Components, I am going to write a new bug report about it.
Another downside of all these new prefetch requests is that they compete with the LCP element making this Core Web Vitals metric worse than it was before v16
Our team globally disabled prefetch and we got a boost in our PageSpeed Insights score in v15 (we went from ~85 points to ~98)
Another downside of all these new prefetch requests is that they compete with the LCP element making this Core Web Vitals metric worse than it was before v16
Our team globally disabled prefetch and we got a boost in our PageSpeed Insights score in v15 (we went from ~85 points to ~98)
I confirm that this extra prefetching is affecting the Core Web Vitals score.
Our team globally disabled prefetch
@carlos-dubon, how did you do this?
@kachkaev
Our team globally disabled prefetch
@carlos-dubon, how did you do this?
I wrapped Next.js' Link component a long time ago. So it was pretty easy to disable it globally.
This is how my current Link.tsx file looks like, feel free to modify it as you need:
'use client';
// eslint-disable-next-line no-restricted-imports
import NextLink from 'next/link';
import NProgress from 'nprogress';
import { useEffect, useTransition } from 'react';
import { useRouter } from 'next/navigation';
const isModifiedClick = (e: React.MouseEvent) => {
return e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0;
};
type LinkProps = {
hideProgressBar?: boolean;
preventNavigation?: boolean;
stopPropagation?: boolean;
} & Parameters<typeof NextLink>[0];
export default function Link({ hideProgressBar, preventNavigation, stopPropagation, ...props }: LinkProps) {
const router = useRouter();
const [isPending, startTransition] = useTransition();
useEffect(() => {
if (hideProgressBar) return;
if (isPending) {
NProgress.start();
} else {
NProgress.done();
}
}, [isPending, hideProgressBar]);
return (
<NextLink
{...props}
prefetch={false}
onClick={e => {
props.onClick?.(e);
if (props.target === '_blank' || isModifiedClick(e)) {
return;
}
e.preventDefault();
if (preventNavigation) return;
if (stopPropagation) e.stopPropagation();
startTransition(() => {
const url = props.href.toString();
const options = {
scroll: props.scroll ?? true,
};
if (props.replace) {
router.replace(url, options);
} else {
router.push(url, options);
}
});
}}
>
{props.children}
</NextLink>
);
}