EdgeCases Logo
Nov 2025
Next.js
Deep
8 min read

Next.js 16 Incremental Prefetching: Smart Route Preloading

Layout deduplication, viewport-based cancellation, and interaction prioritization reduce redundant requests

nextjs
prefetching
performance
routing
optimization
caching
edge-case
next16

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 past

Requests 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 prefetches

Hover 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 data

Real-World Performance Impact

E-commerce Product Grid (100 Links)

MetricNext.js 15Next.js 16
Layout downloads100 (1× per link)1 (deduplicated)
Total data transferred~5 MB~1.2 MB
Prefetch requests200 (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] page

Layout 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 together

3. 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 background

Prefetch Priority System

Next.js 16 uses a three-tier priority system:

  1. High priority (immediate): User clicks/taps link
  2. Medium priority: User hovers/focuses link (cancels low-priority prefetches)
  3. 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

  1. Enable for navigation-heavy UIs: Product grids, blog archives, documentation sites benefit most from prefetching
  2. Disable for auth-gated content: Avoid wasting bandwidth on routes users can't access
  3. Monitor data transfer: Use Chrome DevTools to ensure prefetching doesn't double your bandwidth usage
  4. Respect user preferences: Check navigator.connection.saveData and disable prefetch on constrained connections
  5. 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

Related Insights

Explore related edge cases and patterns

Next.js
Surface
Next.js 16: Dynamic by Default, Turbopack Stable, proxy.ts
8 min
Next.js
Deep
Next.js 'use cache': Explicit Caching with Automatic Keys
9 min

Advertisement