mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
## What? Adds the Instant Insight + rule page for the Partial Prefetching shell validation (Janka's #95151): with `partialPrefetching` on, a route that reads `params`/`searchParams` outside `<Suspense>` blocks the extraction of the per-route **App Shell** that every link to the route shares. Surfaced as an Instant insight (not a redbox), with two fix cards: Stream (wrap in `<Suspense>`) · Allow blocking route (`instant = false`). The insight fires both at link-prefetch time and during a client-side navigation, so it covers a `<Link prefetch>` into the route as well as a plain client navigation to it. Demos: - [99-link-params-gsp](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/99-link-params-gsp) — `<Link prefetch>` to a `generateStaticParams` route that awaits `params` unguarded. - [117-subnav-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/117-subnav-params) — client-side navigation into a `[slug]` route that reads `params` outside `<Suspense>`. - [118-subnav-search-params](https://error-messages-overhaul-ibsl.labs.vercel.dev/scenario/118-subnav-search-params) — client-side navigation into a route that reads `searchParams` outside `<Suspense>`. Follow-up: #95389 documents the route-side audit in the Adopting Partial Prefetching guide (draft, to be coordinated). ## How? - Family-wide polish across all 16 insight pages while aligning the new one: consistent "Verifying the fix" wording, normalized `Learn more:` link texts (target title for guides, bare code name for API references), "App Shell" used consistently, CLS guidance reframed as "minimizing layout shift", prose cleanups (no semicolons, no code-initial sentences), and the insight-error-page skill updated to match. - New `errors/instant-shell-url-data.mdx` rule page, structured like the rest of the insight family (`blocking-prerender-client-hook`, `instant-link-prefetch-partial`); added to every page's Related Insights. New **URL data** glossary entry ("varies per link, not per session"), linked from the page. - `createLinkBodyErrorInNavigation` re-pointed to the page and classified as a navigation-time insight (`inNavigation` → Instant tab) using the shared `linkCards` fix cards. - Common insight format for the message: headline "Next.js encountered URL data outside of Suspense", consequence "may prevent the navigation from being instant, leading to a slower user experience" ("may", since boundaries or a runtime prefetch can still keep the navigation instant), and the two fixes phrased as `[stream] Provide a placeholder with <Suspense fallback={...}> around the data access` and `[block] Set export const instant = false to allow a blocking route`. The shell mechanics are explained on the docs page rather than in the headline. - Second fix reframed from "Disable validation" (group `ignore`) to **Allow blocking route** (group `block`), matching the `blocking-prerender-*` family: `instant = false` marks the segment as allowed to block rather than opting the route out of validation. - Metadata/viewport variants: only the headline term is aligned ("link data" → "URL data") plus a small grammar fix; framing, fix cards, and docs URLs stay on the existing `-metadata-runtime`/`-viewport-runtime` pages unchanged. - `errors.json` regenerated append-only (codes 1407-1409), and the docs opt-out wording updated to the new silence-free phrasing. - e2e snapshots updated for all four configs (dev/start × default/partial-prefetching); unit tests for the matcher, cards, classification, and `isInstantNavigationError`. <!-- NEXT_JS_LLM_PR -->
205 lines
9.5 KiB
Plaintext
205 lines
9.5 KiB
Plaintext
---
|
|
title: Next.js could not validate that a segment in your UI has instant navigation
|
|
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](https://preview.nextjs.org/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](https://preview.nextjs.org/docs/app/glossary#prerendering), a segment in the route tree was dropped from rendering. With [Cache Components](https://preview.nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents) enabled, Next.js validates that every segment can produce an [instant navigation](https://preview.nextjs.org/docs/app/guides/instant-navigation). When a segment is not rendered, that validation can't run and issues that would prevent instant navigation go undetected.
|
|
|
|
This typically happens when a layout conditionally omits `{children}` or a parallel route slot is not rendered.
|
|
|
|
## Ways to fix this
|
|
|
|
<FixCardGrid>
|
|
<FixCard
|
|
group="render"
|
|
title="Render the dropped segment"
|
|
href="#render-the-dropped-segment"
|
|
snippets={[
|
|
{
|
|
text: 'function Layout({ children }) {',
|
|
parts: [
|
|
{ text: 'function Layout({ ' },
|
|
{ text: 'children', highlight: true },
|
|
{ text: ' }) {' },
|
|
],
|
|
},
|
|
{
|
|
text: ' return <><Nav />{children}</>',
|
|
parts: [
|
|
{ text: ' return <><Nav />{' },
|
|
{ text: 'children', highlight: true },
|
|
{ text: '}</>' },
|
|
],
|
|
},
|
|
{ text: '}' },
|
|
]}
|
|
/>
|
|
<FixCard
|
|
group="ignore"
|
|
title="Skip validation on the segment"
|
|
href="#skip-validation-on-the-segment"
|
|
snippets={[
|
|
{ text: '// page.tsx or layout.tsx' },
|
|
{ text: '' },
|
|
{ text: 'export const instant = false', highlight: true },
|
|
]}
|
|
/>
|
|
</FixCardGrid>
|
|
|
|
## Render the dropped segment
|
|
|
|
Choose this fix when the segment should be part of the render tree. The layout that owns the segment needs to render `{children}` (or the parallel route slot prop) so Next.js can validate the subtree for instant navigation.
|
|
|
|
### Patterns
|
|
|
|
#### Render `{children}` in the layout
|
|
|
|
Make sure the layout always includes `{children}` in its output. If the layout conditionally shows different content (a login page when unauthenticated, a dashboard when authenticated), render `{children}` in both branches and handle the conditional inside the child segment.
|
|
|
|
```jsx filename="app/dashboard/layout.js"
|
|
export default function DashboardLayout({ children }) {
|
|
return (
|
|
<>
|
|
<Nav />
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
#### Render the parallel route slot
|
|
|
|
When the dropped segment is a parallel route (e.g. `@modal`), the layout must render the slot prop. If the slot should be hidden in certain states, render it conditionally inside the slot's own page rather than omitting the prop from the layout.
|
|
|
|
```jsx filename="app/dashboard/layout.js"
|
|
export default function DashboardLayout({ children, modal }) {
|
|
return (
|
|
<>
|
|
{children}
|
|
{modal}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
Learn more: [Parallel Routes](/docs/app/api-reference/file-conventions/parallel-routes).
|
|
|
|
#### Move auth or guard checks into the page
|
|
|
|
A common cause is a layout that conditionally returns a sign-in screen (or redirects) instead of rendering `{children}`. Layouts and pages render separately, so put the guard at the page (or slot) level rather than in the layout. The layout always renders `{children}`. Each page decides whether to render its content or redirect.
|
|
|
|
```jsx filename="app/dashboard/layout.js"
|
|
export default function DashboardLayout({ children }) {
|
|
return (
|
|
<>
|
|
<Nav />
|
|
{children}
|
|
</>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="app/dashboard/page.js"
|
|
import { redirect } from 'next/navigation'
|
|
import { getSession } from '@/lib/session'
|
|
|
|
export default async function DashboardPage() {
|
|
const session = await getSession()
|
|
if (!session) redirect('/login')
|
|
return <Dashboard session={session} />
|
|
}
|
|
```
|
|
|
|
Learn more: [Authentication](https://preview.nextjs.org/docs/app/guides/authentication).
|
|
|
|
### Trade-off
|
|
|
|
The segment is always in the render tree, which means Next.js validates it on every dev render. If the segment has dynamic data, it needs its own [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary or caching strategy to stay prerenderable.
|
|
|
|
### Gotchas
|
|
|
|
- A layout that conditionally returns early without rendering `{children}` (e.g. a redirect guard) drops every segment in the subtree. Move the guard into a wrapper component inside `{children}` instead.
|
|
- A Client Component that returns `null` during SSR also drops its children from the render tree, triggering this error. Use a [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary above the Client Component so the fallback renders in place of the skipped subtree.
|
|
|
|
## Skip validation on the segment
|
|
|
|
Choose this fix when the segment is intentionally not rendered in certain states (a modal that only appears on interaction, a slot gated by authentication). Setting [`instant`](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` on the dropped segment tells Next.js to skip validation for it.
|
|
|
|
### Patterns
|
|
|
|
#### Opt the dropped segment out
|
|
|
|
Add the export to the page or layout file of the segment that was dropped from rendering.
|
|
|
|
```jsx filename="app/dashboard/@modal/page.js"
|
|
export const instant = false
|
|
|
|
export default function ModalPage() {
|
|
return <Modal />
|
|
}
|
|
```
|
|
|
|
Learn more: [Route segment `instant` config](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant).
|
|
|
|
### Trade-off
|
|
|
|
The segment is exempt from instant-navigation validation. If it has issues that would block navigation (uncached data outside Suspense, runtime APIs), those issues won't be caught during development.
|
|
|
|
### Gotchas
|
|
|
|
- The export must be on the **dropped segment's own file** (page or layout), not on a parent. The framework walks top-down and the first explicit config wins.
|
|
- Setting [`instant`](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant) to `false` does not disable [prerendering](https://preview.nextjs.org/docs/app/glossary#prerendering). The segment still prerenders if it can. It only disables the validation error.
|
|
|
|
## Verifying the fix
|
|
|
|
After applying a fix, navigate to the route and confirm the insight no longer appears in the dev overlay and the page immediately paints meaningful UI, with any `<Suspense>` fallbacks covering only the regions that stream in. A [`<Suspense>`](https://react.dev/reference/react/Suspense) boundary around the whole page body can pass validation with an empty shell, which defeats the point of an instant navigation.
|
|
|
|
Depending on your [validation level](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults), this may only surface in development.
|
|
|
|
## Don't want this validation?
|
|
|
|
Instant-navigation validation runs by default in [Cache Components](https://preview.nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents) apps and is what surfaces this error.
|
|
|
|
- **One segment**: add [`export const instant = false`](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant) to the page or layout file. This opts out the segment itself. Child segments are still validated during client navigations.
|
|
- **Entire app**: set [`experimental.instantInsights.validationLevel`](https://preview.nextjs.org/docs/app/api-reference/file-conventions/route-segment-config/instant#configuring-validation-defaults) to `'manual-warning'` in `next.config`. This limits validation to segments that explicitly export `instant`.
|
|
|
|
See [Ensuring instant navigations](https://preview.nextjs.org/docs/app/guides/instant-navigation) for the full model.
|
|
|
|
## Related Insights
|
|
|
|
- [Runtime data during prerendering](/docs/messages/blocking-prerender-runtime)
|
|
- [Uncached data during prerendering](/docs/messages/blocking-prerender-dynamic)
|
|
- [URL data in a Client Component outside of Suspense](/docs/messages/blocking-prerender-client-hook)
|
|
- [Runtime data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-runtime)
|
|
- [Uncached data in `generateMetadata()`](/docs/messages/blocking-prerender-metadata-dynamic)
|
|
- [Runtime data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-runtime)
|
|
- [Uncached data in `generateViewport()`](/docs/messages/blocking-prerender-viewport-dynamic)
|
|
- [`Math.random()` while prerendering](/docs/messages/blocking-prerender-random)
|
|
- [`Math.random()` in a Client Component](/docs/messages/blocking-prerender-random-client)
|
|
- [`Date.now()` while prerendering](/docs/messages/blocking-prerender-current-time)
|
|
- [`Date.now()` in a Client Component](/docs/messages/blocking-prerender-current-time-client)
|
|
- [Crypto APIs while prerendering](/docs/messages/blocking-prerender-crypto)
|
|
- [Crypto APIs in a Client Component](/docs/messages/blocking-prerender-crypto-client)
|
|
- [Dynamic data during prefetching](/docs/messages/instant-link-prefetch-partial)
|
|
- [URL data outside of Suspense](/docs/messages/instant-shell-url-data)
|