skip to content

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?

level: middleimportance: should knowfreq 62%

answer

  1. it happens before the click, not on it
  2. viewport, not hover, is the trigger
  3. invisible in dev builds
  4. one prop turns it off per link
  5. how much and how long are version-dependent

basics

~20 s

Next 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 s

In 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
tsx
'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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context