next.js app router

How does Next.js 16 prefetch behave?

The prefetch prop on <Link> determines what gets loaded before and after a click — and whether your loading state ever renders at all. This matrix covers all combinations of route type, loading boundary presence, and prefetch behaviour.

Important: With the browser network tools panel open, hard reload the browser before clicking each link.
Dynamic · with loading.tsx

force-dynamic | loading.tsx ✓ | varies: prefetch prop

prefetch={null}
Default
Partial prefetch
Recommended
viewport
layout.tsx
loading.tsx
~15ms
click
page.tsx + data
~2s

loading.tsx renders immediately on click — users see a spinner while page data loads in the background.

Try demo
prefetch={false}
Disabled
No prefetch
Slow Navigation
viewport
nothing fetched
click
layout.tsx
loading.tsx
page.tsx + data
~2s

Navigation is delayed until the server responds. loading.tsx is fetched at click time — and may never render if the page loads first.

Try demo
prefetch={true}
Full
Full prefetch
Bandwidth heavy
viewport
layout.tsx + loading.tsx + page.tsx + data
~2s
click
instant
instant

Everything is loaded on hover. Clicking navigates instantly to the full page — no loading state shown at all.

Try demo
Dynamic · without loading.tsx

force-dynamic | loading.tsx ✗ | varies: prefetch prop

prefetch={null}
No Loading, Default
No prefetch at all
No prefetch
viewport
nothing fetched
click
layout.tsx + page.tsx + data
~2s

No loading boundary means no partial prefetch. Clicking blocks navigation on the current page until the server responds — no loading state is ever shown.

Try demo
prefetch={true}
No Loading, Force Prefetch
Full prefetch forced
Bandwidth heavy
viewport
layout.tsx + page.tsx + data
~2s
click
instant
instant

prefetch=true forces a full prefetch even without loading.tsx. Navigation is instant but no loading state is possible — the full page data must be fetched upfront on hover.

Try demo
Loading boundary placement

force-dynamic | loading.tsx in parent segment only | prefetch={null}

prefetch={null}
Parent Boundary
Boundary placement
Boundary placement
viewport
parent layout + loading.tsx
~15ms
click
child page.tsx + data
~2s

The partial prefetch only reaches the nearest loading boundary — which is in the parent segment. The child's page.tsx is still fetched on click. Placing loading.tsx closer to the page gives more granular control.

Try demo
Static routes

static render (no force-dynamic) | varies: prefetch | varies: loading.tsx

prefetch={null}
Static + Loading
Always prefetched
Always prefetched
viewport
full page
~instant
click
instant
instant

Static routes are always fully prefetched — layout, loading.tsx, and page data. The loading state is prefetched but never shown because navigation is instant. Router Cache TTL: 5 min.

Try demo
prefetch={false}
Static, Disabled
No prefetch
No prefetch
viewport
nothing fetched
click
full page (edge)
~50ms

prefetch={false} disables prefetching even for static routes. The page is fetched from the edge CDN on click — fast, but not instant. No loading state is shown.

Try demo
key insights

prefetch={null} is usually right

Prefetch is triggered by viewport entry. Next.js loads only the shell — fast and cheap. The loading state renders instantly on click while page data streams in.

⚠️

prefetch={false} has a trap

Both loading.tsx and page.tsx are fetched on click. If page loads first, the loading state never shows — wasted bandwidth.

💡

prefetch={true} is expensive

Pre-fetches everything on viewport entry, including all page data. Instant navigation — but costs bandwidth for every link in view.

🔒

No loading.tsx blocks navigation

Without a loading boundary, partial prefetch is impossible. The browser stays on the current page until the server fully responds.

📦

Static routes are fully prefetched by default

With prefetch={null} or prefetch={true}, Next.js fully prefetches static routes on viewport entry — layout, loading.tsx, and page data. Setting prefetch={false} still disables prefetching.

📐

Boundary placement matters

The partial prefetch stops at the nearest loading.tsx. Place it close to the page component for the most granular loading control.