How does prefetching work for `<Link>` in the Next.js App Router — what triggers it, and how do you turn it off for a single link?
answer
- it happens before the click, not on it
- viewport, not hover, is the trigger
- invisible in dev builds
- one prop turns it off per link
- how much and how long are version-dependent
basics
~20 sNext prefetches a Link's destination in the background when the link scrolls into the viewport, in production builds only, and stores the result in the client-side Router Cache. Setting prefetch={false} on that Link disables it.
solid answer
~40 sIn a production build, `<Link>` prefetches its destination when the anchor enters the viewport — Next observes visibility and fetches the destination's RSC payload in the background, storing it in the in-memory client Router Cache. When the user clicks, the router usually already has the payload, so the transition has no network wait. Prefetching is disabled in `next dev`, which is why it looks broken locally. You opt one link out with `prefetch={false}`, and you can force eager full prefetching with `prefetch={true}`; you can also prefetch imperatively with `router.prefetch(href)` from `useRouter`. How *much* is prefetched — the full route versus just down to the nearest loading boundary — and how long the cached entry stays fresh have both changed between Next majors, so I check the version rather than quoting a default.
code
tsx · 17 lines'use client';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
export function ReportRow({ id, name }: { id: string; name: string }) {
const router = useRouter();
const href = `/reports/${id}`;
return (
<li onMouseEnter={() => router.prefetch(href)}>
<Link href={href} prefetch={false}>
{name}
</Link>
</li>
);
}go deeper
Know that <Link> loads the next route ahead of time so clicks feel instant, and that prefetch={false} switches it off for one link. Remember it does not run in development.
Explain the trigger and the destination of the data: viewport entry starts a background request for the route's RSC payload, which lands in the in-memory client Router Cache and is consumed on click.
Show cost awareness — hundreds of on-screen links mean hundreds of server renders. Be ready to describe opting out and replacing broadcast prefetch with intent-based router.prefetch() on hover or focus.
Own the tradeoff at the system level: prefetching converts user latency into server load and bandwidth. Decide where that trade pays, set the policy for list-heavy pages, and make sure capacity planning counts prefetch traffic.
## Why prefetching exists A client-side navigation still needs data from the server: the RSC payload for the segments that change. That round trip is the whole latency budget of a link click. Prefetching moves that round trip to *before* the click, while the user is still reading, so the click itself resolves from memory. ## What triggers it For `<Link>` in the App Router, the primary trigger is **visibility**: when the anchor scrolls into the user's viewport, Next kicks off a background request for the destination. It is not hover-only — a link sitting on screen is prefetched even if the pointer never touches it. The requests are low priority and are skipped on very slow or data-saver connections. Two things routinely confuse people here: - **Prefetching is disabled in development.** Running `next dev` and watching the network tab produces no prefetch requests. Test this against `next build && next start`. - **Prefetched payloads live in the client Router Cache**, an in-memory, per-session structure. A hard reload empties it. It is not the HTTP cache and not a service worker. You can also prefetch imperatively: ```tsx 'use client'; import { useRouter } from 'next/navigation'; export function Row({ href }: { href: string }) { const router = useRouter(); return <tr onMouseEnter={() => router.prefetch(href)}>{/* … */}</tr>; } ``` That is the escape hatch for destinations that are not behind a `<Link>` at all — a row that navigates on click, a wizard's next step chosen programmatically. ## Controlling it per link The `prefetch` prop takes three values: - **`prefetch={false}`** — do not prefetch this link. Use it for links that are numerous and rarely clicked, or whose destination is expensive to render on the server. - **`prefetch={true}`** — prefetch the full route eagerly, rather than whatever the default partial strategy is. - **default (unset)** — Next's automatic strategy, which differs for statically and dynamically rendered destinations. ```tsx <Link href={`/reports/${id}`} prefetch={false}> {name} </Link> ``` ## The version-sensitive part This is where stale interview answers show up. Two specifics have moved across Next majors and are worth stating as "depends on the version" rather than as a fact: 1. **How much of the destination is prefetched by default.** The general shape is that a fully static destination can be prefetched completely, while a dynamic one is prefetched only down to the nearest loading boundary, so the shell appears instantly and the dynamic part streams after the click. 2. **How long a prefetched entry stays reusable.** Next 15 set the client Router Cache stale time for dynamic pages to `0` by default, meaning a prefetched dynamic entry is used for the instant shell but the data is refetched on navigation. That is tunable through the `experimental.staleTimes` option in `next.config`. Assume Next 15/16 App Router for the numbers above. In an interview, the strong answer names the *mechanism* — a client-side cache of RSC payloads, keyed by route, populated ahead of the click — and flags the defaults as version-dependent, rather than asserting a number that changed two releases ago. ## When to switch it off Prefetching costs server work and bandwidth on requests the user may never make. Turn it off when: - a page renders **hundreds of links** into the viewport, such as a long table or an infinite feed — the aggregate prefetch traffic can rival the page itself; - the destination is **expensive to render** on the server, e.g. a heavy report that hits several upstream services; - the destination has **side effects on GET**, which is a design smell in its own right but does happen with legacy endpoints. In those cases `prefetch={false}` plus an imperative `router.prefetch()` on hover or focus gives you speed on the links people actually aim at, without the broadcast cost. ## Common misconceptions Prefetching does not download the destination's HTML page, and it does not run its client-side effects. It does not warm any server cache you control. And it is not a substitute for making a slow route fast — a route that takes two seconds to render on the server still takes two seconds if the user clicks before the prefetch settles.
- You added prefetching but see no prefetch requests in the network tab. What do you check first?Whether you are running `next dev`. Prefetching is disabled in development, so the correct test is `next build && next start`. After that, check that the link is actually scrolled into the viewport, that `prefetch={false}` is not set, and that the browser is not on a data-saver or very slow connection, where Next skips prefetching.
- A table renders 500 links and the prefetch traffic is hurting the server. What do you do?Set `prefetch={false}` on the row links so visibility no longer triggers fetches, then prefetch selectively — call `router.prefetch(href)` from `useRouter` on row hover or focus. You get instant navigation for links the user is aiming at, and pay nothing for the 490 they scroll past.
- Does prefetching also warm the server-side caches for that route?Not as a guarantee you should rely on. A prefetch is a real request for the route's RSC payload, so whatever the server does while handling it applies — but the artefact you are promised is the entry in the client's in-memory Router Cache. Server-side caching is configured on the server, not implied by a Link prop.
saying these in an interview costs you the question
- Says prefetching only happens on hover
- Expects to see prefetch requests while running next dev
- Claims Link prefetches the destination's HTML page
- Quotes a fixed cache lifetime as if it never changed
- Thinks prefetching makes a slow server route fast