Next.js prefetch, measured: Partial Prefetching in 16.3

Partial Prefetching in Next.js 16.3 cut prefetched bytes by up to 93% in our benchmark, but URL content now lands after the click. When each setup wins.

Published: Reading time: 8 min read

Next.js 16.3 (opens in a new tab) changes what Next.js prefetch means. With partialPrefetching (opens in a new tab) on, the App Router (opens in a new tab) no longer fetches a payload for every link on screen: it fetches one reusable shell per route and leaves the URL-specific content for after the click. We built one app six ways and clicked through it 3,744 times to see what that trade buys and what it costs.

On a docs page with a 100-link sidebar, prefetched bytes fell by 93%. The price is time: the content that used to be on screen almost at once now arrives a few hundred milliseconds after the click, behind a skeleton. Which side of that trade you want depends on the page, and on one detail of React that is easy to miss.

fewer bytes prefetched on a docs page
93%
fewer bytes for a whole docs navigation
11×
skeleton time, pinned by React
298ms
content paint with prefetch={true}
43ms

What Partial Prefetching changes

Until 16.3, the App Router prefetched per link: as N links scrolled into view, it fetched about N route payloads. With partialPrefetching (it requires cacheComponents (opens in a new tab)), it fetches one App Shell (opens in a new tab) per route: everything the page renders that doesn't depend on the URL. Links to the same route share that one prefetch. Content that reads params or searchParams resolves after the click, unless a link asks for more with prefetch={true}.

We compared six builds of the same app:

  • L0, no Cache Components: the classic App Router.
  • C0, Cache Components alone.
  • P0, Cache Components with Partial Prefetching and the default prefetch inlining.
  • P1–P3, the same with inlining off, with small thresholds and with large ones.

Each was clicked through three navigations: a catalog of 48 products to a product page, a docs page with a 100-link sidebar to another docs page, and a search results page to another search through one of eight prefetch={true} links.

Prefetch bytes: up to 93% fewer

On the docs page, Cache Components alone prefetched every visible sidebar link. Partial Prefetching took that from 402 kB29 kB−93%better, in 8.5 requests instead of 49. On the catalog the saving was 78%. Search barely moved, because its eight prefetch={true} links still fetch their own results.

One row deserves a second look. Turning on Cache Components without Partial Prefetching made the catalog prefetch five times more than the classic router, 30 kB160 kB+426%worse: its prerendered product shells become prefetchable per link. If you adopt Cache Components on link-heavy pages, decide on Partial Prefetching at the same time.

Is navigation actually instant?

The shell is. Under Partial Prefetching it painted 43ms45ms after the click on a throttled phone. The part people came for, the product or the article, is another story: it needs a request after the click.

Cache Components alone shows the content at once; Partial Prefetching after a round trip

Click to the first frame with the URL-specific content · mobile, Lighthouse-mobile throttling, warm click · ms

  • L0 · Classic router
  • C0 · Cache Components
  • P0 · Partial Prefetching
Catalog → product375ms · 60ms · 262ms
Docs → docs47ms · 52ms · 345ms
Search → search40ms · 41ms · 43ms
Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
ItemL0 · Classic routerC0 · Cache ComponentsP0 · Partial Prefetching
Catalog → product375ms60ms262ms
Docs → docs47ms52ms345ms
Search → search40ms41ms43ms
Cache Components alone shows the content at once; Partial Prefetching after a round trip. Click to the first frame with the URL-specific content · mobile, Lighthouse-mobile throttling, warm click · ms

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

On docs, Cache Components alone painted the new article in 52ms; Partial Prefetching took 345ms. On the product page the gap was smaller, 60ms262ms+337%worse, and the classic router was slowest of all, because it had nothing prerendered for the dynamic route. Search, where every link carries prefetch={true}, painted in 40ms43ms in every setup.

The skeleton you will see: 300 ms

On the product page the three stages land one after another: the shell, the product, then the part rendered at request time.

Under Partial Prefetching the page arrives in three steps

Catalog → product · mobile, throttled, warm · click to each stage on screen · ms

  • Shell
  • Content
  • Request-time part
L0 · Classic router375ms · 375ms · 375ms
C0 · Cache Components60ms · 60ms · 342ms
P0 · Partial Prefetching47ms · 262ms · 562ms
Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
ItemShellContentRequest-time part
L0 · Classic router375ms375ms375ms
C0 · Cache Components60ms60ms342ms
P0 · Partial Prefetching47ms262ms562ms
Under Partial Prefetching the page arrives in three steps. Catalog → product · mobile, throttled, warm · click to each stage on screen · ms

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

On docs, the skeleton stayed up 298ms in every Partial Prefetching build. That is not network time: the response had arrived after 250ms.

An early click

Clicking 50 ms after the page hydrates, before any prefetch finishes, takes the advantage away from everyone who prefetches. Cache Components alone is as slow as Partial Prefetching then, because both show a fallback first and then wait out the 300 ms.

Clicked early, no setup has prefetched yet

Click 50 ms after hydration · mobile, throttled · click to URL content · ms

  • L0 · Classic router
  • C0 · Cache Components
  • P0 · Partial Prefetching
Catalog → product367ms · 374ms · 389ms
Docs → docs262ms · 528ms · 529ms
Search → search234ms · 404ms · 339ms
Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
ItemL0 · Classic routerC0 · Cache ComponentsP0 · Partial Prefetching
Catalog → product367ms374ms389ms
Docs → docs262ms528ms529ms
Search → search234ms404ms339ms
Clicked early, no setup has prefetched yet. Click 50 ms after hydration · mobile, throttled · click to URL content · ms

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

The last stage, the part rendered at request time, splits these clicks in two. With Partial Prefetching, 6 of 12 early clicks on the product page had the whole page on screen after 375ms400ms. The other 6 took 650ms700ms, and no run landed in between. The gap is about the 300 ms React waits between two reveals: the last stage arrives with the product or one wait later.

The last stage lands with the product or about 300 ms later

Catalog → product, early click · Partial Prefetching · mobile, throttled, HTTP/2 run · click to the last stage on screen · ms

Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
Rangeruns
375ms–400ms6
400ms–425ms0
425ms–450ms0
450ms–475ms0
475ms–500ms0
500ms–525ms0
525ms–550ms0
550ms–575ms0
575ms–600ms0
600ms–625ms0
625ms–650ms0
650ms–675ms3
675ms–700ms3
The last stage lands with the product or about 300 ms later. Catalog → product, early click · Partial Prefetching · mobile, throttled, HTTP/2 run · click to the last stage on screen · ms

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

The whole bill

Partial Prefetching moves bytes from before the click to after it, and on the product page it also doubles them: the navigation response carried two complete copies of the product's payload, 13 kB26 kB+99%worse compressed. Counting everything a navigation downloads, prefetch included, it still comes out far ahead on docs.

A whole docs navigation costs about a tenth of the bytes

All compressed RSC bytes of one docs navigation, prefetch and navigation · mobile, throttled, warm

L0 · Classic router463 kB
C0 · Cache Components458 kB
P0 · Partial Prefetching40 kB
P1 · Inlining off43 kB
P2 · Small inlining39 kB
P3 · Large inlining44 kB
Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
ItemValuen
L0 · Classic router463 kB20
C0 · Cache Components458 kB20
P0 · Partial Prefetching40 kB20
P1 · Inlining off43 kB20
P2 · Small inlining39 kB20
P3 · Large inlining44 kB20
A whole docs navigation costs about a tenth of the bytes. All compressed RSC bytes of one docs navigation, prefetch and navigation · mobile, throttled, warm

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

About 11 times fewer bytes on docs, 2.2 times fewer on the catalog. Hydration and input delay on the start page didn't change in any setup.

When prefetch={true} is worth it

<Link prefetch={true}> (opens in a new tab) fetches the destination's URL data too, so its click is as instant as a full prefetch. It is paid per visible link: the eight search links cost 107 kB, more than the whole docs page under Partial Prefetching. Use it where the click is likely and the data cacheable: search suggestions, a "next" link, the first few results. On grids of cards, prefetch on intent (hover or touch) instead of marking every card.

prefetchInlining: leave it on

experimental.prefetchInlining (opens in a new tab) bundles small prefetch responses into one. The default and our two threshold settings differed by at most one request and a few kilobytes, with no measurable difference in time.

Prefetch requests and compressed bytes by inlining setting · desktop, unthrottled, warm · medians
SetupScenarioRequestsPrefetched
P0 · Partial PrefetchingCatalog → product1134.4 kB
P0 · Partial PrefetchingDocs → docs8.529.4 kB
P0 · Partial PrefetchingSearch → search17106.5 kB
P1 · Inlining offCatalog → product1633.4 kB
P1 · Inlining offDocs → docs732 kB
P1 · Inlining offSearch → search23105 kB
P2 · Small inliningCatalog → product1131.8 kB
P2 · Small inliningDocs → docs727.9 kB
P2 · Small inliningSearch → search17102.8 kB
P3 · Large inliningCatalog → product1033.8 kB
P3 · Large inliningDocs → docs733.1 kB
P3 · Large inliningSearch → search16105.1 kB
Download CSV

Turning inlining off added five or six requests on the catalog and search, and over HTTP/1.1 that cost an early click on the catalog: six connections per host fill up. Over HTTP/2 (opens in a new tab) the penalty disappeared. Every other setup was the same on both protocols.

HTTP/2 only matters with inlining turned off

Catalog → product, early click · mobile, throttled · click to content · HTTP/1.1 and HTTP/2 interleaved, 12 runs each · ms

  • HTTP/1.1
  • HTTP/2
L0 · Classic router365ms · 363ms
C0 · Cache Components375ms · 373ms
P0 · Partial Prefetching387ms · 386ms
P1 · Inlining off440ms · 372ms
P2 · Small inlining390ms · 383ms
P3 · Large inlining387ms · 386ms
Source: Outofplace benchmark, Next.js 16.3.6Download CSVDownload PNG
Show the data
ItemHTTP/1.1HTTP/2
L0 · Classic router365ms363ms
C0 · Cache Components375ms373ms
P0 · Partial Prefetching387ms386ms
P1 · Inlining off440ms372ms
P2 · Small inlining390ms383ms
P3 · Large inlining387ms386ms
HTTP/2 only matters with inlining turned off. Catalog → product, early click · mobile, throttled · click to content · HTTP/1.1 and HTTP/2 interleaved, 12 runs each · ms

The chart as an image, free to reuse under CC BY 4.0 with credit to Outofplace and a link to this page.

Which setup to choose

Cache Components alone

  • URL content on screen at once after a warm click
  • One prefetch per visible link
  • Hundreds of kilobytes on link-heavy pages

With Partial Prefetching

  • Shell at once, URL content after a round trip
  • One prefetch per route
  • 78–93% fewer prefetched bytes where links pile up
next.config.ts
const nextConfig: NextConfig = {
  cacheComponents: true,
  // One App Shell per route instead of one prefetch per link.
  partialPrefetching: true,
};
The links where a wait would hurt
<Link href={`/search?q=${suggestion}`} prefetch={true}>
  {suggestion}
</Link>
  1. Count the links

    Pages with many links to the same dynamic route (docs, grids, feeds) gain the most. A page with a handful of links to different pages gains little.

  2. Decide per route

    Where instant content matters more than bytes and the pages are static, keep a route on full prefetch; elsewhere turn Partial Prefetching on. The segment config export const prefetch = 'partial' (opens in a new tab) adopts it one route at a time.

  3. Mark the likely clicks

    Add prefetch={true} where the next click is predictable and the data cacheable.

  4. Design the skeleton

    It will be seen for about 300 ms on a slow connection. Put the title and navigation in the part that doesn't depend on the URL: it paints almost at once.

This site runs Partial Prefetching: most of our pages have few links, and the ones that have many (the blog, the case studies) are static and small. Our own products, Portivo, Sprawna Matura and Levera, are built on Next.js too.

How we measured

What this can't tell you

It is a lab: one machine, localhost, no CDN, no images or fonts, generated text, and a synthetic 300 ms request-time delay. Throttling is per request, not packet-level, so absolute times are optimistic; the comparisons are what count. A late click is the best case for prefetching and an early one a specific race; real users click everywhere in between. The App Shell mechanics, the doubled product payload and the 300 ms reveal are specific to Next.js 16.3.6 and its React canary and may change in a patch release.

The App Shell arrives through a real link

The shared shell is fetched through the URL of whichever link the router schedules first, and that response carries the whole page for that URL. A click on exactly that link needs no request at all. We picked click targets further down the page so that this didn't flatter Partial Prefetching.

What is prefetch in Next.js?

When a <Link> scrolls into view, the App Router fetches the destination's React Server Components payload in the background, so a click can render without waiting for the network. Next.js 16.3 adds Partial Prefetching, which fetches one reusable shell per route instead of a payload per link.

Is Next.js navigation instant with Partial Prefetching?

The shell is: it painted in about 45ms on a throttled phone in our benchmark. The URL-specific content comes after the click, about 262ms later on a product page and 345ms on a docs page, where Cache Components alone painted it in about 52ms. Links with prefetch={true} stay instant.

Should I disable prefetch in Next.js?

Usually not. Prefetching made warm clicks up to 300 ms faster and did not slow hydration or input. If prefetch traffic is the problem, Partial Prefetching cuts it by up to 93% on link-heavy pages without giving up the shell.

What is the difference between Partial Prefetching and Partial Prerendering?

Partial Prerendering decides what the server renders ahead of time: a static shell with dynamic holes. Partial Prefetching decides what the browser fetches before a click: one shell per route, with URL data resolved after navigation. Partial Prefetching needs Cache Components.

Does HTTP/2 matter for Next.js prefetching?

Barely, as long as prefetch inlining is on (the default). In our HTTP/2 arm the only large difference was with inlining turned off and an early click, where HTTP/2 removed a 68 ms penalty caused by HTTP/1.1's six connections per host.

Further reading

Next.js: Adopting Partial Prefetching (opens in a new tab)How the App Shell works and how to move routes over one at a time.nextjs.org
Next.js: Optimizing prefetching (opens in a new tab)Per-link prefetching with prefetch={true}, and when to use it.nextjs.org
Next.js: prefetchInlining (opens in a new tab)What inlining bundles, and why the default is right for most apps.nextjs.org

Tell us what we’re building.

We reply within one business day.