Overview

A navigation is instant when the browser starts rendering the new page at the click, with static, cached, and fallback content visible while the server streams the rest. Next.js 16.3 (August 2026) adds opt-in tooling for this, and the Next.js team says its behaviors will become the default in a future major version. The setup requires Cache Components; this page covers the structure rules and tools.

Enable both flags

// next.config.ts
export default { cacheComponents: true, partialPrefetching: true };

With Partial Prefetching, each visible <Link> prefetches its destination’s App Shell by default; links to the same route share one shell request. Set prefetch={true} on a link to also prefetch cached content that depends on that link’s URL data (params, searchParams); this costs a server invocation per link. Make the route instant through its App Shell first, because per-link prefetching cannot fix a route that blocks without it.

Know why a page load and a client navigation differ

A direct visit receives the static shell as HTML, and every <Suspense> boundary in the full tree applies. A client navigation re-renders only below the layout the two routes share, so a boundary above that layout, such as one in the root layout, does not cover it. A route can pass on page load and still block on navigation. Client hooks differ too: useSearchParams() suspends on page load but resolves synchronously during a client navigation.

Put the wait behind a boundary or a cache

Every async read below the shared layout needs one of two things: a <Suspense> boundary whose fallback joins the shell, or a "use cache" scope with a lifetime so the result joins the shell. Read params inside the boundary rather than at the top of a layout (see nextjs-async-dynamic-apis). Wrap useSearchParams() in <Suspense> even in a "use client" page. A "use client" page navigates like a single-page app, but the shell still needs its boundaries.

export default function ProductPage(props: PageProps<"/store/[slug]">) {
  return (
    <>
      <Suspense fallback={<p>Loading product...</p>}><ProductInfo params={props.params} /></Suspense>
      <Suspense fallback={<p>Checking availability...</p>}><Inventory params={props.params} /></Suspense>
    </>
  );
}

Here ProductInfo reads a "use cache" function and Inventory queries fresh data.

Follow the dev validation and defer what you cannot fix yet

In development, Cache Components validates each page and default segment for both page loads and client navigations, and the dev overlay reports a blocking-route insight with the fix: cache the data or wrap it in <Suspense>. Set export const instant = false on a segment to mark it allowed to block while you migrate; it does not clear synchronous I/O errors such as Date.now() or Math.random(). Use the Next.js DevTools Navigation Inspector, with Pause on navigations on, to freeze a load or navigation at its shell and see what users see.

Lock the behavior in with instant()

Install @next/playwright and wrap a navigation in instant(page, async () => { ... }) to assert what is visible without waiting for the network. The test fails when a refactor, such as a cookies() read added to a shared header, makes the navigation slow again. See nextjs-caching for the underlying model.