Skip to content

What Next.js 16.3's Instant Navigations Validates

"Instant" isn't a performance budget you hit by trying harder. It's a pass/fail check on whether a route's shell can render without touching the network, and a single misplaced Suspense boundary is enough to fail it.
Daine Mawer||6 min read|1,196 words

The short answer

Next.js 16.3's Instant Navigations feature renders a cached route shell immediately on click, built on the cacheComponents and partialPrefetching flags. It only works when a route's static shell renders without blocking data, and validation fails if a misplaced Suspense boundary wraps dynamic content a click would otherwise still need to stream in.

Next.js 16.3 shipped a feature called Instant Navigations in August 2026, and the name invites the wrong mental model. It isn't a budget you hit by trimming bundle size or shaving milliseconds off a database query. It's closer to a contract: a route either renders its shell without touching the network, or it doesn't, and Next.js now has a way to check which one is true and fail loudly when it isn't.

That distinction matters because most of the adoption friction isn't the feature itself. The two flags it needs take a few seconds to add. What takes longer is restructuring a page so the thing Next.js calls its "shell" is genuinely free of blocking data, and knowing where validation will catch you when it isn't.

What "instant" means here

An instant navigation isn't a full page, pre-rendered and swapped in on click. It's whatever fraction of the destination route Next.js can resolve synchronously: static markup, anything derivable purely from the URL or search params, and anything already resolved through 'use cache'. That fraction gets prefetched ahead of the click and shown immediately. Everything else, anything that still depends on a live fetch, streams in afterward behind its own Suspense boundary, the same way it would without the feature.

So "instant" describes the shell, not the page. A product page with a cached layout and a client-fetched price still counts as instant if the layout shows up immediately and the price streams in a beat later. A product page that blocks its entire body on that same price fetch doesn't, because there's nothing left to show synchronously.

The two flags underneath it

Both pieces are still preview features in 16.3, set at the top level of next.config.ts:

// next.config.ts
const nextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
};

cacheComponents is the prerequisite. partialPrefetching depends on it and won't do anything meaningful without it. If you'd rather not flip this on for the whole app at once, a single route can opt in on its own with a route-level export:

export const prefetch = 'partial';

That's a reasonable way to try the feature against one or two high-traffic routes before deciding whether it's worth the restructuring elsewhere.

Before Partial Prefetching, Next.js prefetched a full copy of the destination page for every link pointing at it. Partial Prefetching changes the unit: instead of one prefetch per link, it builds one reusable shell per route and serves that same shell to every link that points there. That's a meaningful difference on a page with a long list of links to the same handful of destinations, a nav bar, a product grid, a paginated feed, since the shell only needs to be built once rather than once per instance of the link.

The tradeoff shows up with data that depends on the link itself rather than the route. Prefetching with prefetch={true} on an individual link can pull URL-dependent data into that shell ahead of time, and if that data is time-sensitive, cached, or only valid at the moment of the click, it can end up stale by the time the user clicks. This isn't a bug so much as the cost of the shell being built before the click happens at all. Routes with volatile per-link data are exactly the ones where validating the shell's contents carefully matters most.

Where it breaks in practice

The failure mode worth knowing in advance isn't a missing Suspense boundary. It's a Suspense boundary placed around too much. Wrapping an entire page body in one boundary can technically satisfy Next.js's validation while still shipping an empty shell on first paint, since the boundary makes the whole page "pending" rather than isolating the one piece of it that's actually waiting on data. Next.js's own documentation calls this out directly as a way to pass the check and still ship a regression.

The second trap is scope. Validation treats a route's initial page load and its client-side navigations as two separate checks, not one. A Suspense boundary at the root layout can cover the first load fine, but navigating between sibling pages under that same layout re-renders only the page segment below it, not the layout itself, so the boundary that worked for the first case does nothing for the second. A route can pass validation on direct load and still fail it the moment a user clicks into it from a sibling page.

When a route needs to be exempted rather than fixed, that's a route-segment config, not a workaround:

export const instant = false;

Setting it on a layout turns off validation for every route beneath it, so the documentation's own advice is to place it as low in the route tree as will solve the problem, not at the root out of convenience.

Catching regressions with the instant() test helper

Because the Suspense-boundary failure modes above are easy to introduce without anyone noticing in a code review, 16.3 also ships a Playwright helper for this specifically: instant(), from the @next/playwright package. It wraps a click and a set of assertions in a callback, and during that callback, navigation is only allowed to render content that's already cached or prefetched, no network response is permitted through. That lets a test assert exactly what should be visible on screen before any data has loaded, and the test fails the moment a later change breaks that guarantee, whatever the cause.

It's gated outside of test environments by default: production builds disable it unless you explicitly opt in through experimental.exposeTestingApiInProductionBuild, which the documentation is clear is meant for CI and preview deployments, not a production build serving real traffic.

Vercel's own writeup of using this internally on v0 is a useful proof of the workflow rather than a reason to adopt the pattern as described: an agent wrote failing instant() tests against the existing UI, then iterated by moving dynamic fetches behind narrower Suspense boundaries until the tests passed. The mechanical part of that loop, write the test, find the boundary that's too wide, narrow it, is the same one a human does by hand.

Worth adopting now?

Both flags are explicitly previews, not stable defaults, and the validation behavior is new enough that the edge cases above are still being documented as people hit them. That argues for trying it on a small number of high-value routes rather than flipping both flags on for an entire app in one change. A product listing page or a paginated feed with many links to the same handful of destinations is the kind of route where the shell-reuse benefit is largest and easiest to verify. A route with volatile per-link data, or one already struggling with Suspense boundary placement, is a worse first candidate, not because the feature doesn't apply, but because the places it will catch you are the same places it's hardest to fix on the first try.

Further reading

Takeaways

  1. Instant Navigations shipped in Next.js 16.3 (August 2026) as a preview feature, built on two opt-in flags in next.config.ts: cacheComponents and partialPrefetching. Neither is on by default yet.
  2. Partial Prefetching changes the unit of prefetching from one page per link to one reusable shell per route. A route only qualifies for that shell if it renders down to something synchronous, derivable from URL or search params, or already cached with 'use cache'.
  3. The most common way to fail validation isn't missing a Suspense boundary, it's placing one wrong. A boundary wrapping an entire page body can satisfy the check while still shipping an empty shell to the user.
  4. Validation treats first load and client-side navigation as separate checks. A boundary at the root layout covers the initial page load but not navigation between sibling routes under that same layout, so a route can pass one check and fail the other.
  5. The new instant() Playwright helper, shipped via @next/playwright, pauses a navigation mid-flight so a test can assert exactly what's on screen before any network response lands, turning a visual regression into a failing test instead of a shipped regression.

Questions

What is Instant Navigations in Next.js 16.3?

It's a feature that renders a cached shell of the next route immediately when a link is clicked, instead of waiting on a network round trip. It's built on two preview flags, cacheComponents and partialPrefetching, and only works for routes whose shell can render without any blocking data fetch.

How do I enable Cache Components and Partial Prefetching in Next.js?

Set cacheComponents: true and partialPrefetching: true at the top level of next.config.ts. Both are still preview features in 16.3, and partialPrefetching depends on cacheComponents being on. You can also opt a single route in with export const prefetch = 'partial' instead of enabling it globally.

Why isn't my Next.js route navigation instant even with the flags on?

The usual cause is a Suspense boundary in the wrong place, most often one wrapping the entire page body, which can technically pass validation while still rendering an empty shell to the user. Check that the boundary sits around just the dynamic piece of the page, not everything above it.

What does the instant() Playwright helper do in Next.js?

It wraps a click and a set of assertions in a callback, during which the page renders only cached and prefetched content, no network responses allowed through. That lets a test assert exactly what's visible the instant a user clicks, so a later change that breaks the instant shell fails a test instead of shipping unnoticed.

New posts by email

Sent when they're published. No spam, unsubscribe anytime.