Next.js 16 Incremental Prefetching: Smart Route Preloading
Layout deduplication, viewport-based cancellation, and interaction prioritization reduce redundant requests
Next.js 16 introduces incremental prefetching: only fetch data segments that aren't already cached, cancel requests when links leave the viewport, and reprioritize on user interaction. Layout deduplication means hovering over 50 product links downloads the shared layout once, not 50 times. This reduces redundant network requests and improves perceived performance.
The Prefetching Problem (Next.js 15)
Previous Next.js versions prefetched entire route segments when links entered the viewport:
<Link href="/products/1">Product 1</Link>
<Link href="/products/2">Product 2</Link>
<Link href="/products/3">Product 3</Link>
// Next.js 15 behavior:
// - Link enters viewport → Fetch /products/1 layout + page data
// - Link enters viewport → Fetch /products/2 layout + page data (duplicate layout!)
// - Link enters viewport → Fetch /products/3 layout + page data (duplicate layout!)Result: 3× redundant layout downloads for identical shared layouts.
Incremental Prefetching (Next.js 16)
Three key improvements:
1. Layout Deduplication
// Next.js 16 behavior:
// - First link enters viewport → Fetch /products/1 layout + page data
// - Second link enters viewport → Fetch /products/2 page data only (layout cached)
// - Third link enters viewport → Fetch /products/3 page data only (layout cached)Shared layouts are fetched once and reused across all prefetched routes in the same layout tree.
2. Viewport-Based Cancellation
// User scrolls quickly through 100 product links
// Next.js 15: All 100 prefetches start, waste bandwidth
// Next.js 16: Only links in viewport prefetch, cancel when scrolled pastRequests are aborted when links exit the viewport, preventing wasted bandwidth on content users aren't viewing.
3. Interaction Prioritization
// User hovers over a link
// Next.js 15: Prefetch at same priority as viewport-based prefetch
// Next.js 16: Reprioritize hovered link to highest priority, pause other prefetchesHover and focus events reprioritize prefetch requests, ensuring user-intended navigation loads fastest.
How It Works: Partial Route Matching
Next.js 16 builds a cache tree structure and only fetches uncached segments:
// Cache state after visiting /products/1
Cache:
/products (layout) ✓
/products/1 (page) ✓
// User hovers /products/2
Prefetch request:
- Skip /products layout (already cached)
- Fetch /products/2 page data only
// User hovers /categories/electronics
Prefetch request:
- Fetch /categories layout (not cached)
- Fetch /categories/electronics page dataReal-World Performance Impact
E-commerce Product Grid (100 Links)
| Metric | Next.js 15 | Next.js 16 |
|---|---|---|
| Layout downloads | 100 (1× per link) | 1 (deduplicated) |
| Total data transferred | ~5 MB | ~1.2 MB |
| Prefetch requests | 200 (layout + page per link) | 101 (1 layout + 100 pages) |
Result: 76% reduction in data transfer for repeated layouts.
Controlling Prefetch Behavior
Disable Prefetching
// Disable per-link
<Link href="/expensive-page" prefetch={false}>
No Prefetch
</Link>
// Disable globally (next.config.ts)
const nextConfig = {
experimental: {
prefetchRoutes: false, // Disable all automatic prefetching
}
};
export default nextConfig;Manual Prefetch Control
import { useRouter } from 'next/navigation';
function ProductCard({ id }) {
const router = useRouter();
return (
<div
onMouseEnter={() => {
// Manually trigger prefetch on hover
router.prefetch(`/products/${id}`);
}}
>
<Link href={`/products/${id}`} prefetch={false}>
Product {id}
</Link>
</div>
);
}Edge Cases and Debugging
1. Dynamic Routes Still Prefetch Full Segments
Dynamic segments ([id]) cannot be deduplicated across different IDs:
// Each ID requires separate prefetch
<Link href="/products/1">Product 1</Link> // Prefetch: layout + [id] page
<Link href="/products/2">Product 2</Link> // Prefetch: layout (cached) + [id] page
<Link href="/products/3">Product 3</Link> // Prefetch: layout (cached) + [id] pageLayout is deduplicated, but page data for each [id] must be fetched individually.
2. Parallel Routes Cancel Together
When using parallel routes (@modal, @sidebar), all segments cancel together when link leaves viewport:
// app/dashboard/@modal/page.tsx
// app/dashboard/@sidebar/page.tsx
// Link enters viewport:
// - Prefetch /dashboard layout
// - Prefetch /dashboard/@modal/page
// - Prefetch /dashboard/@sidebar/page
// Link exits viewport:
// - Cancel all three requests together3. Loading.tsx Affects Prefetch Timing
If route has loading.tsx, Next.js prefetches Suspense boundary separately:
// app/products/[id]/loading.tsx exists
// Prefetch sequence:
// 1. Fetch layout (instant from cache)
// 2. Fetch loading.tsx boundary (immediate render)
// 3. Fetch actual page data (background, shows loading state)This creates perceived instant navigation even for slow data-fetching pages.
4. Cache Expiration Still Applies
Prefetched data expires according to cacheLife profiles. If user hovers a link 20 minutes after initial prefetch and cache expired, refetch occurs:
// Route with default cacheLife (15min stale)
'use cache';
export const cacheLife = 'default';
// t=0: Link enters viewport → Prefetch and cache
// t=10min: User clicks link → Instant navigation (cache fresh)
// t=20min: User clicks link → Stale cache, refetch in backgroundPrefetch Priority System
Next.js 16 uses a three-tier priority system:
- High priority (immediate): User clicks/taps link
- Medium priority: User hovers/focuses link (cancels low-priority prefetches)
- Low priority: Link in viewport (can be canceled by medium/high priority)
// Example: User scrolls past 50 links, then hovers one
// 1. 50 links enter viewport → 50 low-priority prefetches start
// 2. User hovers link #25 → Cancel other 49, prioritize #25 to medium
// 3. User clicks link #25 → Upgrade to high priority (if still fetching)Debugging Prefetch Behavior
Chrome DevTools Network Tab
// Filter by RSC (React Server Components) requests
// Look for requests with:
// - RSC-Prefetch: 1 (prefetch request)
// - RSC-Prefetch: 0 (actual navigation)React DevTools Profiler
Enable "Highlight updates when components render" to see when prefetched data hydrates into UI.
Console Logging
// next.config.ts
const nextConfig = {
logging: {
fetches: {
fullUrl: true, // Log full URLs of prefetch requests
},
},
};
export default nextConfig;When Prefetching Hurts Performance
1. Mobile Data Plans
Aggressive prefetching wastes mobile data. Disable on slow connections:
// Disable prefetch on slow connections
if ('connection' in navigator) {
const connection = (navigator as any).connection;
if (connection.saveData || connection.effectiveType === '2g') {
// Disable prefetch
nextConfig.experimental.prefetchRoutes = false;
}
}2. Large Data Payloads
If route fetches 5MB+ data, prefetching may harm performance:
// Disable prefetch for data-heavy routes
<Link href="/analytics/reports" prefetch={false}>
Reports (Large Download)
</Link>3. Authentication-Required Routes
Prefetching auth-gated routes fails silently, wasting requests:
// Only prefetch if user is logged in
{isLoggedIn ? (
<Link href="/dashboard" prefetch={true}>Dashboard</Link>
) : (
<Link href="/login" prefetch={false}>Login</Link>
)}Prefetch Configuration
// next.config.ts
const nextConfig = {
experimental: {
// Disable all prefetching
prefetchRoutes: false,
// Control prefetch cache TTL (default: 30s)
prefetchCacheLifetime: 60, // Seconds
// Max concurrent prefetch requests (default: 5)
maxPrefetchConcurrency: 10,
}
};
export default nextConfig;Best Practices
- Enable for navigation-heavy UIs: Product grids, blog archives, documentation sites benefit most from prefetching
- Disable for auth-gated content: Avoid wasting bandwidth on routes users can't access
- Monitor data transfer: Use Chrome DevTools to ensure prefetching doesn't double your bandwidth usage
- Respect user preferences: Check
navigator.connection.saveDataand disable prefetch on constrained connections - Prefetch on hover for critical paths: Manually trigger prefetch on hover for your most important CTAs
Incremental prefetching makes Next.js smarter about what it downloads. Layouts are fetched once, unused prefetches are canceled, and user intent reprioritizes requests. The result: faster perceived navigation without wasting bandwidth.
Advertisement
Explore these curated resources to deepen your understanding
Official Documentation
Tools & Utilities
Related Insights
Explore related edge cases and patterns
Advertisement