Next.js prefetching doesn’t just download HTML; it actually runs the JavaScript for the next page in a hidden iframe, making navigation feel instant.

Let’s see what that looks like. Imagine a simple app with two pages: / and /about.

# Project setup (simplified)
npx create-next-app@latest my-prefetch-app
cd my-prefetch-app

In pages/index.js:

import Link from 'next/link';

export default function HomePage() {
  return (
    <div>
      <h1>Home Page</h1>
      <Link href="/about">Go to About Page</Link>
    </div>
  );
}

In pages/about.js:

import Link from 'next/link';

export default function AboutPage() {
  return (
    <div>
      <h1>About Page</h1>
      <p>This is the about page.</p>
      <Link href="/">Go to Home Page</Link>
    </div>
  );
}

When you run npm run dev and navigate to http://localhost:3000, the Link component for /about is rendered. Next.js, by default, will start prefetching that page when it enters the viewport.

To see this in action, open your browser’s developer tools, go to the "Network" tab, and filter by "XHR". When you load the homepage, you’ll see a request for _next/data/development/about.json. This fetches the data for the about page. Crucially, if you inspect the network traffic more closely (or use the React DevTools in the browser), you’ll see that Next.js isn’t just downloading the HTML; it’s also downloading and executing the JavaScript bundle for /about in the background. When you click the "Go to About Page" link, the transition is near-instant because the JavaScript is already "warm" and ready to render.

The magic happens with the next/link component. By default, next/link automatically prefetches pages when they are in the viewport. This prefetching happens in the background, using a low-priority request, and includes both the page’s data (via getStaticProps or getServerSideProps) and its JavaScript bundle.

You can control this behavior. Setting prefetch={false} on a Link component disables prefetching for that specific link:

import Link from 'next/link';

function HomePage() {
  return (
    <div>
      <h1>Home Page</h1>
      {/* This link will NOT prefetch */}
      <Link href="/about" prefetch={false}>
        Go to About Page (No Prefetch)
      </Link>
      <br />
      {/* This link WILL prefetch (default behavior) */}
      <Link href="/contact">Go to Contact Page</Link>
    </div>
  );
}

For production builds, prefetching is enabled by default for links in the viewport. You can also programmatically prefetch pages using useRouter:

import { useRouter } from 'next/router';

function MyComponent() {
  const router = useRouter();

  const prefetchAbout = () => {
    router.prefetch('/about');
  };

  return (
    <button onMouseEnter={prefetchAbout}>
      Hover me to prefetch About
    </button>
  );
}

This router.prefetch('/about') call will trigger the same background download and execution of the /about page’s assets. This is useful for links that might not be immediately visible but are likely to be navigated to soon, like a "Read More" button after some introductory text.

The core problem Next.js prefetching solves is the perceived latency of client-side navigation. Without it, when a user clicks a link, the browser has to:

  1. Request the HTML for the new page.
  2. Parse the HTML.
  3. Download the associated JavaScript bundles.
  4. Execute the JavaScript.
  5. Render the React component.

This sequence can take hundreds of milliseconds, sometimes even seconds on slower networks, leading to a jarring user experience. Prefetching compresses steps 1-4 into the background, so when the user clicks, only step 5 (rendering) remains, making the navigation feel instantaneous.

The actual mechanism involves a IntersectionObserver to detect when links enter the viewport. Once a Link component is observed, Next.js adds the page’s route to a prefetch queue. A background worker then fetches the necessary JSON data and JavaScript chunks. Importantly, the JavaScript is executed to ensure the component is ready, not just downloaded. This is why it feels faster than a simple resource preload; the code is already "hot" in memory.

One aspect that often surprises developers is how aggressively Next.js prefetches. In development mode, it prefetches all links in the viewport. In production, it’s slightly more conservative, but still quite eager. This can lead to significant background data and script downloads, which might be a concern on very low-bandwidth or metered connections. While prefetch={false} is the direct control, understanding that the default is to be as fast as possible is key. If you have a large application with many links on a single page, and many of those links are visible, you might be prefetching a lot of code and data that the user never actually clicks on.

The next hurdle you’ll likely encounter is managing the performance implications of aggressive prefetching in large applications.

Want structured learning?

Take the full Nextjs course →