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
Include content in a per-link prefetch
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, orunstable_cachescope. 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
instanttofalseopts 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.
- One segment: add
export const instant = falseto the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations. - Entire app: set
experimental.instantInsights.validationLevelto'manual-warning'innext.config. This limits validation to segments that explicitly exportinstant.
See Ensuring instant navigations for the full model.
Related Insights
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.