← All notes
Frontend Development6 min

A Link in view is not a prefetch plan

Instant Navigations and Partial Prefetching are a real improvement: a link can warm a reusable loading shell instead of dragging the whole destination along for the ride. The failure mode did not disappear. On a collection page with forty product cards, "prefetch what is in view" still means forty speculative trips unless you decide which links earn bandwidth and which wait for intent.

What Partial Prefetching actually changed

For a long time, App Router prefetch sat in an awkward middle. Hover and viewport heuristics pulled more than a loading UI and less than a finished page, and dense storefronts paid for it in parallel requests. Next.js 16.3's Partial Prefetching is explicit about the split: extract a reusable shell from the destination, let `<Link>` choose how much of the rest to include, and stop pretending every prefetch is the same size.

That pairs with Cache Components and Suspense holes. The shell can be the chrome and shared framing. The holes stay dynamic — price, stock, account-aware bits — and do not have to ride along just because a card scrolled into view on a category page.

Fewer, smaller prefetch payloads are the default win when you upgrade. The remaining work is product-shaped: decide which destinations are worth warming before the click, and which ones should wait until the user means it.

A category grid is a prefetch amplifier

Marketing pages with three CTAs rarely expose the bug. Commerce and catalog pages do. A florist collection, a clinician product index, a related-items rail under a PDP — each card is a Link, and each Link is a chance to start work the shopper never asked for.

On a phone on a mediocre connection, that shows up as jank while the page they are actually reading competes with speculative fetches for pages they scroll past. On the server, it shows up as a burst of RSC/prefetch traffic correlated with browsing a grid, not with completing a purchase.

We treat dense link surfaces as a budget, not a free list. Primary nav, breadcrumbs, and the next step in a funnel can prefetch aggressively. A wall of product cards defaults to intent — hover on desktop where it helps, tap-to-navigate on mobile without warming every sibling in the row.

Warm the shell, not the sellable state

This is the sibling mistake to caching price and copy under one tag. Prefetching a product route should not mean pulling yesterday's availability into a warm client cache just because the card was visible. Prefer shell-first prefetch: layout, shared chrome, and stable framing. Leave price, stock badges, and checkout-readiness for the navigation or a short-lived hole.

If your product card already shows a price from the listing query, that number is a listing claim. It does not obligate the destination prefetch to re-download the full commerce payload for every neighbor card. Listing data answered the browse question. The PDP answers the buy question when they click.

On builds that already keep Shopify as the money system and Sanity as the explanation system, prefetch policy should respect the same seam. Warm the pages that teach. Do not speculative-fetch the commitment surface for forty SKUs at once.

Measure navigations you care about, not every Link

Instant Insights and the Navigation Inspector are useful when they are aimed. Pick the paths that define the business: home to collection, collection to product, product to checkout handoff. Watch whether those feel instant after a shell prefetch — and whether the rest of the page got slower while the grid warmed itself.

A Playwright helper that asserts a critical navigation still shows an instant shell after a refactor is worth more than a blanket `prefetch={true}` on a card component. Prefetch regressions hide inside shared components. One prop default can turn a careful shell strategy into a fan-out.

When something feels slow, check the waterfall before rewriting the route as a client SPA. Soft navigations and view transitions have their place; burning the network on unused prefetches is not a reason to abandon the server-driven model.

A short prefetch checklist for storefront Links

Inventory every shared Link wrapper — product card, collection tile, related rail, search hit. Write down its default prefetch behavior. If you cannot explain why it prefetches, turn it off or narrow it to a shell.

Allowlist eager prefetch for high-intent chrome: main nav, logo home, cart, and the primary CTA on a landing page. Everything in a repeating grid starts from intent or a deliberate hover policy.

After enabling Partial Prefetching, verify one collection page on a throttled mobile profile. If the listing scroll competes with a storm of destination fetches, the plan is still "whatever is in view," not a plan.

Questions

Does Partial Prefetching mean every Link should prefetch again?
No. It means you can prefetch less, more precisely — usually a reusable shell — and choose how much of the destination to include. Dense grids still need an explicit budget.
Should product cards prefetch on viewport entry?
Default to no on mobile and on large grids. Prefer intent (click) or a narrow hover prefetch on desktop for cards the user is clearly aiming at. Save eager prefetch for primary navigation and funnel steps.
How is this different from Cache Components freshness?
Cache Components decide what stays warm on the server and when tags invalidate. Prefetch policy decides what speculative work starts before a click. You need both: fresh sellable state when they arrive, and no fan-out of unused destination fetches while they browse.

Sources

  1. Next.js 16.3 — Partial Prefetching
  2. Next.js — Link prefetch prop
  3. Next.js — Cache Components

Have something to build?

Tell us what you're working on and we'll tell you honestly whether we're the right fit.

Work with us