instant-navigation-stage.mdx


title: Next.js encountered prefetch() or navigation() outside of Suspense kind: insight

<div style={{ padding: '1.25rem 1.5rem', border: '1px solid var(--ds-gray-400)', borderRadius: '12px', background: 'var(--ds-background-200)', margin: '1.5rem 0 2rem', fontSize: '0.95rem', lineHeight: '1.6', }} > This insight is part of the [Instant Navigations](https://nextjs.org/blog/next-16-3-instant-navigations) feature introduced in Next.js 16.3. If you're new to it, start with the [Ensuring instant navigations](/docs/app/guides/instant-navigation) guide for an overview of what instant navigations are and how Next.js validates them, then come back here for the specific fix. </div>

During prerendering or a client-side navigation, a Server Component called prefetch() or navigation() outside of <Suspense>. These navigation stage APIs need a fallback when they exclude a subtree from a prefetch.

During runtime rendering, prefetch() excludes subsequent content from the App Shell but includes it in <Link prefetch={true}> prefetches and navigations. navigation() excludes the content from both the App Shell and per-link prefetches, then renders it during navigation. Neither API affects initial loads or static prerenders, and subsequent content remains cacheable.

For the same insight in metadata or viewport code, see prefetch() or navigation() in generateMetadata() or prefetch() or navigation() in generateViewport().

Ways to fix this

<FixCardGrid> <FixCard group="stream" title="Wrap in or move into Suspense" href="#wrap-in-or-move-into-suspense" snippets={[ { text: '<Suspense fallback={…}>', highlight: true }, { text: ' <DataChild />' }, { text: '</Suspense>', highlight: true }, ]} /> <FixCard group="block" title="Allow blocking route" href="#allow-blocking-route" snippets={[ { text: '// page.tsx or layout.tsx' }, { text: 'export const instant = false', highlight: true }, ]} /> </FixCardGrid>

Wrap in or move into Suspense

Choose this fix when the route has meaningful UI before the deferred content. Place <Suspense> around the smallest region that calls the navigation stage API.

Patterns

Use prefetch() when the region should be absent from the App Shell but available to links that use prefetch={true}.

import { Suspense } from 'react'
import { prefetch } from 'next/cache'

async function Recommendations() {
  await prefetch()
  return <ProductGrid products={await getRecommendations()} />
}

export default function Page() {
  return (
    <main>
      <h1>Products</h1>
      <Suspense fallback={<ProductGridSkeleton />}>
        <Recommendations />
      </Suspense>
    </main>
  )
}

Learn more: prefetch().

Defer content until navigation

Use navigation() when the region should be absent from prefetches and rendered only after navigation begins.

import { Suspense } from 'react'
import { navigation } from 'next/cache'

async function RecentActivity() {
  await navigation()
  return <ActivityList activity={await getRecentActivity()} />
}

export default function Page() {
  return (
    <Dashboard>
      <h1>Overview</h1>
      <Suspense fallback={<ActivitySkeleton />}>
        <RecentActivity />
      </Suspense>
    </Dashboard>
  )
}

Learn more: navigation() and Streaming.

Trade-off

The earlier prefetch contains the fallback instead of the deferred content. A per-link prefetch adds speculative server work for content below prefetch(), while content below navigation() does not begin rendering until navigation.

Gotchas

  • The boundary must be above the component that awaits the navigation stage API. Adding a boundary inside that component does not catch the suspension.
  • Navigation stage APIs cannot run inside a use cache, use cache: private, or unstable_cache scope. Put the cache directive on a function called after the stage boundary.
  • Static prerenders resolve both functions because the work runs once and is shared. Next.js still requires a boundary so the route remains valid if it later needs a runtime App Shell.

Allow blocking route

Choose this fix when the route has no meaningful UI before the deferred content. Setting instant to false exempts the segment from instant-navigation validation.

Patterns

Opt the page out

Add the export to the page that calls the navigation stage API. Only that route is allowed to block.

import { navigation } from 'next/cache'

export const instant = false

export default async function Page() {
  await navigation()
  return <Dashboard />
}

Learn more: Ensuring instant navigations.

Opt the layout out

Add the export to a layout when the shared segment has no useful earlier state.

export const instant = false

export default function DashboardLayout({ children }) {
  return <DashboardShell>{children}</DashboardShell>
}

Learn more: Route segment instant config.

Use either pattern when:

  • The segment intentionally has no meaningful UI before the point selected by the navigation stage API.
  • You're migrating the route incrementally and need to preserve its current blocking behavior.

Don't use this to dismiss the insight. Choose Wrap in or move into Suspense when a useful fallback exists.

Trade-off

Navigations to this route are not instant. The browser waits for the full server render before receiving HTML. Use this only when the route requires that delay.

Gotchas

  • Setting instant to false opts out only the segment that exports it. Descendant segments remain validated by their own config or the global default.
  • This export does not disable prerendering. The route still prerenders if it can. It only disables instant-navigation validation for the route.

Verifying the fix

After applying a fix, navigate to the route and confirm the insight no longer appears. The page should paint meaningful UI immediately. Keep <Suspense> fallbacks scoped to the regions that stream in. A boundary around the whole page can pass validation with an empty shell, which defeats instant navigation.

In next dev, the error overlay identifies the failing component with a file path and line number. The default next build output is shorter. Run next build --debug-prerender for full user-frame stack traces or next build --debug-build-paths /dashboard /settings to inspect specific routes.

Don't want this validation?

Instant-navigation validation runs by default in Cache Components apps and surfaces this insight.

See Ensuring instant navigations for the full model.

See Instant Navigation validation errors for the complete reference.

Rendered from the public repository vercel/next.js on GitHub; content belongs to its authors. Cached for up to an hour.

errors/instant-navigation-stage.mdx · vercel/next.js | Docs MD