Next.js course ยท Module 2: Routing and Layouts
Prefetching and Navigation Optimization
In this lesson10
In the Quantum Metropolis, the advanced transportation system relies on prediction and proactive planning. Q-Link vehicles, before a passenger issues a travel command, already analyze typical routes, prepare the fastest connections, and pre-warm engines to optimal temperature. This "quantum prefetching" technology enables near-instant travel after a command is issued, eliminating delays and ensuring smooth movement throughout the city.
In the world of Next.js 16, similar principles apply to prefetching mechanisms and navigation optimization. These techniques significantly improve user experience, minimize delays, and maximize the performance of our applications.
Understanding Prefetching in Next.js 16
Prefetching is a technique that involves loading resources in the background before the user directly requests them. In Next.js 16, prefetching is built into the Link component and works automatically, but we can also precisely control its behavior.
Automatic Prefetching
In Next.js 16, prefetching is enabled by default for the Link component, but it only works in production (after next build and next start). When a link appears in the browser's viewport, Next.js fetches the destination page's data in the background: the full page for a static route, and for a dynamic route only the part down to the nearest loading.tsx file (without such a file, prefetching of a dynamic route is skipped). Next.js 16 rebuilt this mechanism: a shared layout is downloaded only once for many links, and only the parts that are not in the cache yet are fetched.
// Automatic prefetching of links in the viewport
<Link href="/destinations/mars">
Explore Mars
</Link>This mechanism works like the traffic prediction system in the Quantum Metropolis - when a vehicle approaches an intersection, the system is already planning optimal routes for all possible directions.
Prefetching Control
We can precisely control prefetching behavior using the prefetch property of the Link component:
// Disable prefetching for the link
<Link href="/rarely-visited-page" prefetch={false}>
Rarely visited page
</Link>Disabling prefetching can be useful for links that are rarely used or lead to resource-heavy pages. It's like disabling the pre-preparation system for rarely selected destinations in the Quantum Metropolis, saving energy and resources.
Impact on Application Performance
Prefetching has a significant impact on application performance and user experience:
- Faster navigation - Pages load almost instantly because resources are already pre-loaded.
- Less waiting after a click - the page data is already loaded when the user clicks; the cost is extra requests to the server, also for links nobody clicks.
- Resource savings - Selective use of prefetching saves resources for less important links.
Link Component Optimization
The Link component also has several improvements that further optimize navigation. Some of them have been available since Next.js 13, and Next.js 16 added the more economical prefetching described above.
Automatic Attribute Forwarding to the <a> Tag
Since Next.js 13, the Link component automatically forwards all attributes to the internal <a> tag, which simplifies code and improves SEO:
// Next.js 13+ (including 16) - simplified syntax
<Link
href="/destinations/mars"
className="btn btn-primary"
aria-label="Explore Mars"
target="_blank"
rel="noopener noreferrer"
>
Explore Mars
</Link>This automation works like the intelligent vehicle interfaces in the Quantum Metropolis, which automatically adjust their settings to passenger preferences without the need for manual configuration.
The scroll Option
The Link component also allows controlling page scrolling behavior after navigation:
// Disable automatic scroll to top of page
<Link href="/destinations" scroll={false}>
View Destinations
</Link>This option is particularly useful when we want to preserve the scroll position, e.g., during navigation within a long list or when implementing custom scroll effects.
Links to Dynamic Routes
Effective use of dynamic parameters in Link components enables more flexible navigation:
// Dynamic parameters in the Link component
const destinations = ['mars', 'europa', 'titan'];
return (
<ul>
{destinations.map(destination => (
<li key={destination}>
<Link href={`/destinations/${destination}`}>
Explore {destination.charAt(0).toUpperCase() + destination.slice(1)}
</Link>
</li>
))}
</ul>
);This pattern allows building dynamic navigation menus and lists, similar to the dynamic route system in the Quantum Metropolis, which allows choosing any combination of intermediate points.
In the App Router the object { pathname: '/destinations/[id]', query: { id } } does not put the parameter into the path (it would give /destinations/[id]?id=mars); an object with query is only for query parameters, e.g. { pathname: '/search', query: { q: 'mars' } }.
Programmatic Prefetching with useRouter
In addition to declarative prefetching with the Link component, Next.js 16 also enables programmatic page preloading using the useRouter hook.
Basic Usage of router.prefetch
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function DestinationCard({
destination,
}: {
destination: { id: string; name: string; description: string; isPopular?: boolean };
}) {
const router = useRouter();
// Preload the details page on mouse hover
const handleHover = () => {
router.prefetch(`/destinations/${destination.id}`);
};
// Preload most visited pages on component mount
useEffect(() => {
if (destination.isPopular) {
router.prefetch(`/destinations/${destination.id}`);
}
}, [destination, router]);
return (
<div
className="destination-card"
onMouseEnter={handleHover}
onClick={() => router.push(`/destinations/${destination.id}`)}
>
<h3>{destination.name}</h3>
<p>{destination.description}</p>
</div>
);
}This pattern resembles the intelligent system of the Quantum Metropolis, which prepares vehicles for travel when a resident approaches a station, even before they make the final travel decision.
Strategic Prefetching
In more advanced scenarios, we can apply strategic prefetching based on user behavior analysis:
'use client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function Dashboard({ user }) {
const router = useRouter();
useEffect(() => {
// Prefetching pages based on user history
if (user.mostVisitedDestinations) {
user.mostVisitedDestinations.forEach(destination => {
router.prefetch(`/destinations/${destination.id}`);
});
}
// Prefetching pages based on time of day
const currentHour = new Date().getHours();
if (currentHour >= 9 && currentHour <= 17) {
// During business hours, prefetch business destinations
router.prefetch('/destinations/business');
} else {
// After work hours, prefetch recreational destinations
router.prefetch('/destinations/recreation');
}
// Prefetching pages based on upcoming events
if (user.upcomingTrip) {
router.prefetch(`/trips/${user.upcomingTrip.id}`);
router.prefetch(`/checklist/${user.upcomingTrip.id}`);
}
}, [user, router]);
return (
<div className="dashboard">
{/* Dashboard content */}
</div>
);
}This advanced prefetching strategy resembles the predictive system of the Quantum Metropolis, which analyzes traffic patterns and resident preferences to prepare vehicles and routes in advance.
Data Loading Optimization
In Next.js 16, navigation optimization is closely linked to data loading optimization. The App Router introduces new methods for efficient data fetching and caching.
Loading Data in Server Components
Server Components in Next.js 16 allow loading data directly in the component, without the need for additional APIs:
// app/destinations/[id]/page.tsx
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
// Loading data directly in the component
const destination = await fetchDestination((await params).id);
return (
<div className="destination-page">
<h1>{destination.name}</h1>
<p>{destination.description}</p>
{/* More content */}
</div>
);
}This method eliminates the additional API requests that are typical for traditional React applications. It's similar to the integrated system of the Quantum Metropolis, which combines route planning and vehicle control in a single process, eliminating the need for communication between different systems.
Parallel Data Loading
We can optimize data loading through parallel query execution:
// app/destinations/[id]/page.tsx
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
// Parallel data loading
const [destination, attractions, reviews] = await Promise.all([
fetchDestination((await params).id),
fetchAttractions((await params).id),
fetchReviews((await params).id)
]);
return (
<div className="destination-page">
<h1>{destination.name}</h1>
<section className="attractions">
<h2>Popular Attractions</h2>
<AttractionsList attractions={attractions} />
</section>
<section className="reviews">
<h2>Traveler Reviews</h2>
<ReviewsList reviews={reviews} />
</section>
</div>
);
}This technique resembles the Quantum Metropolis transportation system, which prepares different elements of the trip in parallel - the vehicle, the route, and intermediate points - to shorten the total preparation time.
Using generateMetadata and generateStaticParams
Next.js 16 provides special functions for optimizing data for metadata and static parameters:
// app/destinations/[id]/page.tsx
import type { Metadata } from 'next';
// Function generating page metadata
export async function generateMetadata(
{ params }: { params: Promise<{ id: string }> }
): Promise<Metadata> {
// Fetching data only for metadata
const destination = await fetchDestinationBasic((await params).id);
return {
title: `${destination.name} - Quantum Voyages`,
description: destination.shortDescription,
openGraph: {
images: [{ url: destination.imageUrl }],
},
};
}
// Function generating static parameters for popular destinations
export async function generateStaticParams() {
// Fetching only the most popular destinations for prerendering
const popularDestinations = await fetchPopularDestinations();
return popularDestinations.map((destination) => ({
id: destination.id,
}));
}
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
// Fetching full data for the page
const destination = await fetchDestinationFull((await params).id);
return (
<div className="destination-page">
{/* Page content */}
</div>
);
}These special functions allow selective data loading for different aspects of the page, similar to the Quantum Metropolis system, which allocates resources for different aspects of the trip based on their priorities.
Data Caching Strategies
Next.js 16 offers flexible data caching strategies that can be tailored to the specific needs of each application. The fetch options below belong to the default caching model of Next.js 16 (without cacheComponents enabled). Remember that since Next.js 15 fetch requests are not cached by default, so you turn caching on explicitly.
Static and Dynamic Caching
// app/destinations/page.tsx
export default async function DestinationsPage() {
// Data that rarely changes - static caching
const categories = await fetchCategories({
next: { revalidate: 3600 } // Refresh every hour
});
// Data that changes frequently - dynamic caching
const featuredDestinations = await fetchFeaturedDestinations({
next: { revalidate: 60 } // Refresh every minute
});
// Data unique to each request - no caching
const userRecommendations = await fetchUserRecommendations({
cache: 'no-store'
});
return (
<div className="destinations-page">
<CategoriesList categories={categories} />
<FeaturedDestinations destinations={featuredDestinations} />
<RecommendationsList recommendations={userRecommendations} />
</div>
);
}This flexible caching strategy resembles the Quantum Metropolis transportation system, which caches different types of information with different refresh cycles - permanent information about landmarks is cached longer, while dynamic traffic information is updated more frequently.
On-Demand Revalidation
Next.js 16 also enables on-demand revalidation of cached data, which is useful after data updates. Note the second argument of revalidateTag: since Next.js 16 it is a cacheLife profile, and the recommended value 'max' means the user gets the old version while the fresh one loads in the background. The single-argument form is deprecated:
// app/api/revalidate/route.ts
import { NextRequest } from 'next/server';
import { revalidatePath, revalidateTag } from 'next/cache';
export async function GET(request: NextRequest) {
const tag = request.nextUrl.searchParams.get('tag');
const path = request.nextUrl.searchParams.get('path');
if (tag) {
revalidateTag(tag, 'max');
return Response.json({ revalidated: true, tag });
}
if (path) {
revalidatePath(path);
return Response.json({ revalidated: true, path });
}
return Response.json({ revalidated: false, message: 'No tag or path provided' }, { status: 400 });
}This functionality works like the route update system in the Quantum Metropolis, which can instantly propagate information about changes in city infrastructure to all vehicles, without waiting for regular update cycles.
Component Rendering Optimization
Navigation optimization in Next.js 16 also includes component rendering optimization.
Streaming and Suspense
Next.js 16 supports streaming and Suspense, which allow for progressive rendering of the user interface:
// app/destinations/[id]/page.tsx
import { Suspense } from 'react';
import DestinationHeader from './DestinationHeader';
import AttractionsList from './AttractionsList';
import WeatherWidget from './WeatherWidget';
import ReviewsList from './ReviewsList';
import RelatedDestinations from './RelatedDestinations';
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
// Fetching basic information needed right away
const destinationInfo = await fetchDestinationInfo((await params).id);
return (
<div className="destination-page">
<DestinationHeader info={destinationInfo} />
<div className="destination-content grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2">
{/* Component that may load slowly */}
<Suspense fallback={<div className="attractions-loading">Loading attractions...</div>}>
<AttractionsList destinationId={(await params).id} />
</Suspense>
{/* Another component with potentially long loading time */}
<Suspense fallback={<div className="reviews-loading">Loading reviews...</div>}>
<ReviewsList destinationId={(await params).id} />
</Suspense>
</div>
<div className="lg:col-span-1">
{/* Components in the right column */}
<Suspense fallback={<div className="weather-loading">Loading weather...</div>}>
<WeatherWidget destinationId={(await params).id} />
</Suspense>
<Suspense fallback={<div className="related-loading">Loading related destinations...</div>}>
<RelatedDestinations currentId={(await params).id} />
</Suspense>
</div>
</div>
</div>
);
}This technique resembles the Quantum Metropolis transportation system, which allows passengers to board the vehicle and start the journey, even if some onboard systems (e.g., entertainment or route information) are still loading.
Selective Rendering with Client and Server Components
Properly separating components into Server Components and Client Components can significantly improve performance:
// app/destinations/[id]/page.tsx - Server Component
import { fetchDestination } from '@/lib/data';
import DestinationDetails from './DestinationDetails';
import InteractiveMap from './InteractiveMap'; // Client Component
import TripPlanner from './TripPlanner'; // Client Component
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
// Loading data on the server
const destination = await fetchDestination((await params).id);
return (
<div className="destination-page">
{/* Server Component - rendered on the server */}
<DestinationDetails destination={destination} />
{/* Client Components - rendered interactively on the client side */}
<div className="interactive-components mt-8 flex flex-col lg:flex-row gap-6">
<InteractiveMap
coordinates={destination.coordinates}
points={destination.interestPoints}
/>
<TripPlanner
destinationId={destination.id}
initialDates={destination.availableDates}
/>
</div>
</div>
);
}This hybrid rendering strategy works like the optimized system of the Quantum Metropolis, which combines elements of centralized planning (Server Components) with autonomous vehicle behavior (Client Components) to optimize the entire experience.
Image and Static Asset Optimization
Static asset optimization is crucial for fast navigation in Next.js 16 applications.
The Image Component
Next.js 16 provides the Image component, which automatically optimizes images:
// app/destinations/[id]/page.tsx
import Image from 'next/image';
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
const destination = await fetchDestination((await params).id);
return (
<div className="destination-page">
<div className="destination-hero relative h-96 mb-8">
<Image
src={destination.imageUrl}
alt={destination.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
preload={true} // Loading with high priority
className="object-cover rounded-lg"
/>
</div>
<h1 className="text-4xl font-bold mb-4">{destination.name}</h1>
<p className="text-lg mb-8">{destination.description}</p>
<div className="attractions grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{destination.attractions.map(attraction => (
<div key={attraction.id} className="attraction-card relative h-64">
<Image
src={attraction.imageUrl}
alt={attraction.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover rounded-lg"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/70 to-transparent rounded-lg">
<div className="absolute bottom-0 p-4 text-white">
<h3 className="text-xl font-semibold">{attraction.name}</h3>
</div>
</div>
</div>
))}
</div>
</div>
);
}The Image component automatically optimizes images by adjusting their size, format, and loading to the display context, similar to the Quantum Metropolis visual system, which dynamically adjusts visual information to the device and user preferences.
The preload Attribute (formerly priority)
The preload attribute in the Image component allows setting image loading priorities. In Next.js 16 it replaced the deprecated priority attribute and works the same way: it adds a <link rel="preload"> tag to the <head>, so the image starts downloading right away. Use it only for the single most important image (LCP); in other cases the documentation recommends loading="eager" or fetchPriority="high":
// Critical image for LCP (Largest Contentful Paint)
<Image
src="/hero-image.jpg"
alt="Discover Space with Quantum Voyages"
width={1200}
height={600}
preload={true}
/>This feature works like the priority system in the Quantum Metropolis, which directs more resources to the most important aspects of the trip, ensuring optimal power and bandwidth distribution.
Static Image Imports
Next.js 16 also enables static image imports, allowing for automatic optimization and typing:
// Static image import
import marsImage from '@/public/images/destinations/mars.jpg';
export default function MarsCard() {
return (
<div className="destination-card">
<Image
src={marsImage}
alt="Mars - the red planet"
/>
<h3>Mars</h3>
<p>The Red Planet with potential for future colonies.</p>
</div>
);
}This technique resembles the Quantum Metropolis visual identity system, which stores pre-optimized images for all standard city components, ensuring consistent and efficient presentation.
Advanced Prefetching Strategies
Advanced prefetching applications can significantly improve application performance.
Conditional Prefetching Based on User Interaction
'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
export default function DestinationCard({
destination,
}: {
destination: { id: string; name: string; description: string; isPopular?: boolean };
}) {
const router = useRouter();
const [interactionCount, setInteractionCount] = useState(0);
const [interactionTime, setInteractionTime] = useState(0);
// Tracking user interaction
const trackInteraction = () => {
setInteractionCount(prev => prev + 1);
setInteractionTime(prev => prev + 0.5); // Simulate time spent hovering over card
};
useEffect(() => {
// Prefetch only if the user shows sufficient interest
if (interactionCount > 2 || interactionTime > 1.5) {
router.prefetch(`/destinations/${destination.id}`);
}
}, [interactionCount, interactionTime, destination.id, router]);
return (
<div
className="destination-card"
onMouseEnter={trackInteraction}
onMouseMove={trackInteraction}
onClick={() => router.push(`/destinations/${destination.id}`)}
>
<h3>{destination.name}</h3>
<p>{destination.description}</p>
</div>
);
}This advanced strategy works like the adaptive prediction system of the Quantum Metropolis, which analyzes a resident's level of interest in a specific direction and adjusts preparations accordingly.
Prefetching Based on Navigation Patterns
We can analyze typical user navigation patterns and prefetch pages they're likely to visit:
'use client';
import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
export default function NavigationAnalytics() {
const router = useRouter();
const pathname = usePathname();
useEffect(() => {
// Determine next probable paths based on the current path
const predictNextPages = (currentPath) => {
// Navigation patterns determined from user analytics
const navigationPatterns = {
'/destinations': ['/destinations/mars', '/destinations/europa', '/booking'],
'/destinations/mars': ['/booking/mars', '/destinations/europa', '/destinations'],
'/booking': ['/checkout', '/destinations', '/account'],
'/checkout': ['/confirmation', '/dashboard', '/account'],
};
return navigationPatterns[currentPath] || [];
};
// Prefetching predicted paths
const nextProbablePaths = predictNextPages(pathname);
nextProbablePaths.forEach(path => {
router.prefetch(path);
});
}, [pathname, router]);
// This component renders nothing, only analyzes navigation
return null;
}This system resembles the advanced route prediction in the Quantum Metropolis, which analyzes typical resident travel patterns and prepares routes they're most likely to choose, based on historical data and context.
Monitoring and Optimizing Web Vitals
Monitoring Web Vitals metrics is crucial for navigation optimization and overall user experience.
Tracking Web Vitals
Next.js 16 offers built-in tools for tracking Web Vitals:
// app/_components/web-vitals.tsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitalsReporter() {
useReportWebVitals((metric) => {
// Sending metrics to the analytics system
console.log(metric);
});
return null;
}The root layout stays a Server Component and only renders this component, because hooks such as useReportWebVitals work only in a Client Component:
// app/layout.tsx (Server Component)
import { WebVitalsReporter } from './_components/web-vitals';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<WebVitalsReporter />
{children}
</body>
</html>
);
}This functionality works like the Quantum Metropolis monitoring system, which tracks all aspects of the journey, from vehicle waiting time to ride smoothness, to continuously optimize the resident experience.
Optimizing Largest Contentful Paint (LCP)
Optimizing LCP is crucial for improving perceived loading speed:
// app/page.tsx
import Image from 'next/image';
import heroImage from '@/public/images/hero.jpg';
export default function HomePage() {
return (
<div className="home-page">
{/* LCP optimization through priority loading of main image */}
<div className="hero-section relative h-[80vh]">
<Image
src={heroImage}
alt="Quantum Voyages - Your gateway to space"
fill
preload={true} // Important for LCP
className="object-cover"
/>
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-center text-white">
<h1 className="text-5xl font-bold mb-4">Discover Space with Quantum Voyages</h1>
<p className="text-xl mb-8">Your gateway to interplanetary travel</p>
<button className="bg-indigo-600 hover:bg-indigo-700 text-white py-3 px-8 rounded-full">
Start Your Journey
</button>
</div>
</div>
</div>
{/* Remaining page content */}
</div>
);
}This optimization technique works like the first impression system in the Quantum Metropolis, which ensures that the most important elements of the travel experience are delivered first, creating a positive first impression.
Minimizing Cumulative Layout Shift (CLS)
Reducing layout shifts is crucial for a smooth experience:
// app/destinations/[id]/page.tsx
import Image from 'next/image';
export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
const destination = await fetchDestination((await params).id);
return (
<div className="destination-page">
{/* Preventing CLS by specifying image dimensions */}
<div className="destination-hero relative aspect-[16/9] mb-8">
<Image
src={destination.imageUrl}
alt={destination.name}
fill
sizes="100vw"
className="object-cover rounded-lg"
/>
</div>
{/* Preventing CLS by reserving space for dynamic content */}
<div className="content-container min-h-[500px]">
<h1 className="text-4xl font-bold mb-4">{destination.name}</h1>
<p className="text-lg mb-8">{destination.description}</p>
{/* More content */}
</div>
</div>
);
}This technique resembles the Quantum Metropolis stabilization system, which ensures that city elements don't shift unexpectedly, even when new information or services are added in real time.
Summary
Prefetching and navigation optimization in Next.js 16 are powerful tools that significantly improve user experience and application performance. Like the advanced Quantum Metropolis transportation system, which uses "quantum prefetching" technology to eliminate delays, Next.js 16 offers a range of features and strategies that enable creating extremely responsive and efficient applications:
Automatic prefetching - the
Linkcomponent automatically loads resources in the background when a link appears in the viewport.Programmatic prefetching - the
useRouterhook enables strategic page preloading based on user interaction, navigation patterns, and other factors.Data loading optimization - Server Components, parallel queries, and flexible caching strategies minimize delays associated with data fetching.
Selective rendering - proper use of Server and Client Components enables optimal separation of rendering logic.
Streaming and Suspense - progressive rendering of the user interface provides a responsive experience even while loading slower elements.
Static asset optimization - the
Imagecomponent and other tools automatically optimize images and other assets.Web Vitals monitoring and optimization - tools for tracking and improving key performance metrics.
By applying these techniques, our Quantum Voyages application can offer near-instant navigation and a smooth experience, similar to the advanced Quantum Metropolis transportation system, which eliminates delays and ensures smooth movement throughout the city.
In the next lesson you will see how to change the URL without reloading the page (shallow routing).
Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do you disable automatic prefetching for a single Link component?
2. Which useRouter method is used to programmatically prefetch a page (e.g. on mouse hover)?
Hands-on tasks in the game
- Horizontal ordering
Arrange the opening syntax of a Link component that disables automatic scroll to the top