What prefetch={true} means now
The old full-route prefetch is gone. What a default link warms now, the redefined prefetch={true}, the segment cost ceiling, and the silent regression to watch for.
The prefetch you remember no longer exists
Under the old model a visible <Link> prefetched the whole destination: the static parts fully, the dynamic parts up to the nearest loading boundary. Partial Prefetching redefines both the default and the prefetch={true} prop, and the change is easy to miss because nothing errors.
Default link: now warms only the shared App Shell (the static and session output the route shares), not the whole page.
prefetch={true}: no longer "prefetch everything." It now means "additionally resolve this link's URL data (params, searchParams) before the click."
Turn it on
partialPrefetching builds on cacheComponents; enable them together. The shell you structured in Lesson 3 is exactly what a link now prefetches by default, with no prop.
// next.config.tsconst nextConfig: NextConfig = { cacheComponents: true, partialPrefetching: true, // depends on cacheComponents}The regression to watch for
If a page relied on the old full prefetch, so its per-URL content was ready on the click, a default link now delivers only the App Shell and that content streams in after the click instead. It still works, it is just slower than before, with no warning.
// Default link: warms only the shared App Shell (cheap, one per route).<Link href="/dashboard">Dashboard</Link> // prefetch={true}: NOT "prefetch everything" anymore.// It means "also resolve THIS link's URL data before the click".<Link href={`/posts/${slug}`} prefetch={true}>{title}</Link>A link points to /posts/hello-world. The post body is cached but keyed by the slug. Does a default link prefetch the body?
router.prefetch() call sites do not surface a dev insight, so a preserved "fetch before navigate" behavior can silently degrade to shell-only. Audit those by hand and verify them under next start (prefetching is production-only).
Two dials: intent on the link, cost ceiling on the route
A prefetch has two independent controls. The <Link> prop states intent: should this be prefetched, how eagerly. The prefetch export on the destination sets a cost ceiling: how much any link may pull for this route. A destination cannot know which links point at it, so it caps the work.
// app/posts/[slug]/page.tsx — set on the DESTINATION, not the linkexport const prefetch = 'partial' // adopt one route without the global flag| Value | Meaning |
|---|---|
'auto' | The default; same as omitting it. Do not write it out explicitly. |
'partial' | Adopt one route into Partial Prefetching without the global flag. Default link → App Shell; a prefetch={true} link also resolves URL data. |
'force-disabled' | Never prefetch this segment. For rarely visited pages, e.g. behind auth. |
It does not stop route metadata from being prefetched. And if a parent segment runs a per-link prefetch, deeper segments are included in that same request, even ones marked force-disabled. It caps the segment's own default prefetch, not every path to it.
Cost, and adopting incrementally
Each visible prefetch={true} link can wake a server, so a grid of cards is one render per card as they enter the viewport. Prefetch on hover for dense lists; the default link carries no such cost. To adopt without flipping the whole app, set prefetch = 'partial' per route, then enable the global flag and remove the exports (the remove-partial-prefetch codemod does this).
| Situation | Choice |
|---|---|
| Route shares a static or cached shell. | Default link. It warms the shared shell. |
| Route depends on the URL and that content is worth prefetching. | <Link prefetch={true}> on that link. |
| Whole route depends on the URL, no useful shell. | instant = false. Dynamic by design. |
| Rarely visited page behind auth. | prefetch = 'force-disabled'. |
Your app upgraded to Cache Components and users report product pages feel slower to open, though nothing errors. Detail pages are keyed by [slug] and links are plain <Link>. What is the most likely cause?
What you learned
- A default link now warms the shared App Shell, not the whole route.
prefetch={true}means "resolve this link's URL data," not "prefetch everything."- Relying on the old full prefetch can silently regress; audit
router.prefetch()by hand. - The link states intent; the
prefetchexport sets the cost ceiling. force-disabledstill allows metadata and parent-driven prefetches.
Last lesson: making prefetch={true} pay off, and bringing per-user session data into the shell without leaking it into a shared cache.