Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
0f0aa80
Clear instant validation errors on route change + nav body variants
aurorascharff May 14, 2026
68a47a2
Show navigation-specific headline + explanation in dev overlay
aurorascharff May 14, 2026
7917001
Detect in-navigation errors via stable message substrings
aurorascharff May 14, 2026
aee125a
Detect dev overlay navigation via history events, not usePathname
aurorascharff May 14, 2026
80685ae
Trim comments
aurorascharff May 14, 2026
32b16b9
add missing reducer action
aurorascharff May 14, 2026
bdf8d80
Update tests
aurorascharff May 14, 2026
8ae10a7
Update tests
aurorascharff May 14, 2026
b155b1a
Merge branch 'canary' of github.com:vercel/next.js into aurorascharff…
aurorascharff May 14, 2026
7bbd860
Update snapshots for in-navigation factory wording
aurorascharff May 14, 2026
b221f22
Update build-mode snapshots for in-navigation factory wording
aurorascharff May 14, 2026
bf68fea
Align InNavigation factory wording, extract isBlockingRouteInNavError
aurorascharff May 14, 2026
6e04312
Merge branch 'canary' into aurorascharff/instant-nav-error-clear
aurorascharff May 14, 2026
84c089f
Merge remote-tracking branch 'origin/canary' into aurorascharff/insta…
aurorascharff May 14, 2026
1b2e9c5
Use state.page for clear-on-nav + update unit tests
aurorascharff May 14, 2026
bb7fa35
Fix snapshot indentation and drop test/tmp prefix
aurorascharff May 14, 2026
aa2d6ef
Skip first state.page transition when clearing instant errors on nav
aurorascharff May 14, 2026
d8449f0
Align runtime-body in-navigation fix-order with the SSR variant
aurorascharff May 14, 2026
4e4406d
Fix `code`/`description` indent in level-* snapshots
aurorascharff May 14, 2026
b1876c8
Match sibling indent for code/description in remaining level-* snapshots
aurorascharff May 14, 2026
2b32f5d
Update build-mode CLI snapshots for in-navigation factory wording
aurorascharff May 14, 2026
e69e4ad
Revert SSR-only snapshots in level-manual-warning to initial-render w…
aurorascharff May 14, 2026
e7b2f5b
fix failing tests
aurorascharff May 15, 2026
08f62f9
Handle instant errors more cleanly
aurorascharff May 15, 2026
a579c3d
Merge branch 'canary' of github.com:vercel/next.js into aurorascharff…
aurorascharff May 15, 2026
ca89b0e
Redesign the "could not validate" instant error
aurorascharff May 15, 2026
595dafa
Adjust cards
aurorascharff May 15, 2026
4c6070e
Update
aurorascharff May 15, 2026
6d93b2d
Match dynamic route templates when clearing instant errors on nav
aurorascharff May 15, 2026
b36f9f0
Use shared route-regex helper for template matching
aurorascharff May 15, 2026
f194bd7
Merge branch 'aurorascharff/instant-nav-error-clear' of github.com:ve…
aurorascharff May 15, 2026
78cbf65
Adjust error message
aurorascharff May 15, 2026
d7f2c90
Add page route info
aurorascharff May 15, 2026
13023d9
Add test
aurorascharff May 15, 2026
84f712d
Add "stacktrace" for the dropped segment
aurorascharff May 16, 2026
897010e
Extract reusable component code-frame-shell
aurorascharff May 16, 2026
dcf251d
Clean up styles and wording
aurorascharff May 16, 2026
9ea959e
Yav/redesign unrendered segment overlay followup (#93925)
yavorpunchev May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/next/.storybook/decorators/use-overlay-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ACTION_BUILDING_INDICATOR_HIDE,
ACTION_BUILDING_INDICATOR_SHOW,
ACTION_CACHE_INDICATOR,
ACTION_INSTANT_ERRORS_CLEAR,
ACTION_INSTANT_NAVS_TOGGLE,
ACTION_INSTANT_NAVS_RESET,
ACTION_DEBUG_INFO,
Expand Down Expand Up @@ -88,6 +89,7 @@ export function useStorybookOverlayReducer(initialState?: OverlayState) {
case ACTION_RENDERING_INDICATOR_HIDE:
case ACTION_RENDERING_INDICATOR_SHOW:
case ACTION_CACHE_INDICATOR:
case ACTION_INSTANT_ERRORS_CLEAR:
case ACTION_INSTANT_NAVS_TOGGLE:
case ACTION_INSTANT_NAVS_RESET:
case ACTION_STATIC_INDICATOR:
Expand Down
15 changes: 15 additions & 0 deletions packages/next/.storybook/fixtures/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,18 @@ export const instantClientMathRandomErrors: ReadyRuntimeError[] = [
type: 'runtime',
},
]

export const instantUnrenderedSegmentErrors: ReadyRuntimeError[] = [
{
id: 130,
runtime: true,
error: Object.assign(
new Error(
'Route "/81-instant-wrapper-unrendered-segment/trigger": Could not validate that a segment in your UI has instant navigation.\n\nThis segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.\n\nDropped segment:\n test-app/app/81-instant-wrapper-unrendered-segment/trigger/page.tsx\n\nWays to fix this:\n - Render the dropped segment\n - Set `export const instant = false` on the dropped segment to skip validation\n\nLearn more: https://nextjs.org/docs/messages/unrendered-instant-segment'
),
{ __NEXT_ERROR_CODE: 'E1248' }
),
frames: () => Promise.resolve([]),
type: 'runtime',
},
]
9 changes: 8 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -1244,5 +1244,12 @@
"1243": "This \"use cache\" has a dynamic cache life that was propagated to its parent.",
"1244": "A \"use cache\" with short \\`expire\\` (under 5 minutes) is nested inside another \"use cache\" that has no explicit \\`cacheLife\\`, which is not allowed during prerendering. Add \\`cacheLife()\\` to the outer \"use cache\" to choose whether it should be prerendered (with longer \\`expire\\`) or remain dynamic (with short \\`expire\\`). Read more: https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife",
"1245": "A \"use cache\" with zero \\`revalidate\\` is nested inside another \"use cache\" that has no explicit \\`cacheLife\\`, which is not allowed during prerendering. Add \\`cacheLife()\\` to the outer \"use cache\" to choose whether it should be prerendered (with non-zero \\`revalidate\\`) or remain dynamic (with zero \\`revalidate\\`). Read more: https://nextjs.org/docs/messages/nested-use-cache-no-explicit-cachelife",
"1246": "Could not validate instant UI because an expected segment was not rendered."
"1246": "Route \"%s\": Next.js encountered uncached data during the initial render or a navigation.\\n\\n\\`fetch(...)\\` or \\`connection()\\` accessed under \\`<Suspense>\\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.\\n\\nWays to fix this:\\n - Cache the data access with \\`\"use cache\"\\`\\n - Provide a placeholder with \\`<Suspense fallback={...}>\\` around the data access\\n - Set \\`export const instant = false\\` to allow a blocking route\\n\\nLearn more: https://nextjs.org/docs/messages/blocking-route",
"1247": "Route \"%s\": Next.js encountered runtime data during the initial render or a navigation.\\n\\n\\`cookies()\\`, \\`headers()\\`, \\`params\\`, or \\`searchParams\\` accessed under \\`<Suspense>\\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.\\n\\nWays to fix this:\\n - Use \\`generateStaticParams\\` to make route params static\\n - Provide a placeholder with \\`<Suspense fallback={...}>\\` around the data access\\n - Set \\`export const instant = false\\` to allow a blocking route\\n\\nLearn more: https://nextjs.org/docs/messages/blocking-route",
"1248": "Could not validate instant UI because an expected segment was not rendered.",
"1249": "Route \"%s\": Next.js encountered uncached data during the initial render or a navigation.\\n\\n\\`fetch(...)\\` or \\`connection()\\` accessed outside of \\`<Suspense>\\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.\\n\\nWays to fix this:\\n - Cache the data access with \\`\"use cache\"\\`\\n - Provide a placeholder with \\`<Suspense fallback={...}>\\` around the data access\\n - Set \\`export const instant = false\\` to allow a blocking route\\n\\nLearn more: https://nextjs.org/docs/messages/blocking-route",
"1250": "Route \"%s\": Next.js encountered runtime data during the initial render or a navigation.\\n\\n\\`cookies()\\`, \\`headers()\\`, \\`params\\`, or \\`searchParams\\` accessed outside of \\`<Suspense>\\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.\\n\\nWays to fix this:\\n - Use \\`generateStaticParams\\` to make route params static\\n - Provide a placeholder with \\`<Suspense fallback={...}>\\` around the data access\\n - Set \\`export const instant = false\\` to allow a blocking route\\n\\nLearn more: https://nextjs.org/docs/messages/blocking-route",
"1251": "Route \"%s\": Next.js encountered runtime data during the initial render or a navigation.\\n\\n\\`cookies()\\`, \\`headers()\\`, \\`params\\`, or \\`searchParams\\` accessed outside of \\`<Suspense>\\` prevents the route from being prerendered or the navigation from being instant, leading to a slower user experience.\\n\\nWays to fix this:\\n - Provide a placeholder with \\`<Suspense fallback={...}>\\` around the data access\\n - Use \\`generateStaticParams\\` to make route params static\\n - Set \\`export const instant = false\\` to allow a blocking route\\n\\nLearn more: https://nextjs.org/docs/messages/blocking-route",
"1252": "Route \"%s\": Could not validate instant UI because an expected segment was not rendered.",
"1253": "Route \"%s\": Could not validate that a segment in your UI has instant navigation."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { ReactNode } from 'react'
import { ExternalIcon } from '../../icons/external'

type CodeFrameShellProps = {
header: ReactNode
onOpen?: () => void
openLabel?: string
children: ReactNode
}

export function CodeFrameShell({
header,
onOpen,
openLabel,
children,
}: CodeFrameShellProps) {
return (
<div data-nextjs-codeframe>
<div className="code-frame-header">
<p className="code-frame-link">
{header}
{onOpen && (
<button
aria-label={openLabel ?? 'Open in editor'}
data-with-open-in-editor-link-source-file
onClick={onOpen}
type="button"
>
<ExternalIcon />
</button>
)}
</p>
</div>
<pre className="code-frame-pre">
<div className="code-frame-lines">{children}</div>
</pre>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { useMemo } from 'react'
import { HotlinkedText } from '../hot-linked-text'
import { getStackFrameFile, type StackFrame } from '../../../shared/stack-frame'
import { useOpenInEditor } from '../../utils/use-open-in-editor'
import { ExternalIcon } from '../../icons/external'
import { FileIcon } from '../../icons/file'
import { CodeFrameShell } from './code-frame-shell'
import {
formatCodeFrame,
groupCodeFrameLines,
Expand Down Expand Up @@ -35,69 +35,55 @@ export function CodeFrame({ stackFrame, codeFrame }: CodeFrameProps) {

const fileExtension = stackFrame?.file?.split('.').pop()
return (
<div data-nextjs-codeframe>
<div className="code-frame-header">
{/* TODO: This is <div> in `Terminal` component.
Changing now will require multiple test snapshots updates.
Leaving as <div> as is trivial and does not affect the UI.
Change when the new redbox matcher `toDisplayRedbox` is used.
*/}
<p className="code-frame-link">
<CodeFrameShell
header={
<>
<span className="code-frame-icon">
<FileIcon lang={fileExtension} />
</span>
<span data-text>
{getStackFrameFile(stackFrame)} @{' '}
<HotlinkedText text={stackFrame.methodName} />
</span>
<button
aria-label="Open in editor"
data-with-open-in-editor-link-source-file
onClick={open}
>
<ExternalIcon />
</button>
</p>
</div>
<pre className="code-frame-pre">
<div className="code-frame-lines">
{parsedLineStates.map(({ line, parsedLine }, lineIndex) => {
const { lineNumber, isErroredLine } = parsedLine

const lineNumberProps: Record<string, string | boolean> = {}
if (lineNumber) {
lineNumberProps['data-nextjs-codeframe-line'] = lineNumber
}
if (isErroredLine) {
lineNumberProps['data-nextjs-codeframe-line--errored'] = true
}

return (
<div key={`line-${lineIndex}`} {...lineNumberProps}>
{line.map((entry, entryIndex) => (
<span
key={`frame-${entryIndex}`}
style={{
color: entry.fg ? `var(--color-${entry.fg})` : undefined,
...(entry.decoration === 'bold'
? // TODO(jiwon): This used to be 800, but the symbols like `─┬─` are
// having longer width than expected on Geist Mono font-weight
// above 600, hence a temporary fix is to use 500 for bold.
{ fontWeight: 500 }
: entry.decoration === 'italic'
? { fontStyle: 'italic' }
: undefined),
}}
>
{entry.content}
</span>
))}
</div>
)
})}
</div>
</pre>
</div>
</>
}
onOpen={open}
>
{parsedLineStates.map(({ line, parsedLine }, lineIndex) => {
const { lineNumber, isErroredLine } = parsedLine

const lineNumberProps: Record<string, string | boolean> = {}
if (lineNumber) {
lineNumberProps['data-nextjs-codeframe-line'] = lineNumber
}
if (isErroredLine) {
lineNumberProps['data-nextjs-codeframe-line--errored'] = true
}

return (
<div key={`line-${lineIndex}`} {...lineNumberProps}>
{line.map((entry, entryIndex) => (
<span
key={`frame-${entryIndex}`}
style={{
color: entry.fg ? `var(--color-${entry.fg})` : undefined,
...(entry.decoration === 'bold'
? // TODO(jiwon): This used to be 800, but the symbols like `─┬─` are
// having longer width than expected on Geist Mono font-weight
// above 600, hence a temporary fix is to use 500 for bold.
{ fontWeight: 500 }
: entry.decoration === 'italic'
? { fontStyle: 'italic' }
: undefined),
}}
>
{entry.content}
</span>
))}
</div>
)
})}
</CodeFrameShell>
)
}

Expand Down Expand Up @@ -218,7 +204,7 @@ export const CODE_FRAME_STYLES = `
}

[data-nextjs-codeframe-line] > span:first-child {
color: var(--color-gray-alpha-700) !important;
color: var(--color-gray-alpha-500) !important;
}

[data-nextjs-codeframe-line][data-nextjs-codeframe-line--errored="true"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type FixCardGroup =
| 'client'
| 'defer'
| 'measure'
| 'silence'
| 'render'

export type FixCardIcon =
| 'align-left'
Expand All @@ -35,6 +37,8 @@ export const FIX_CARD_GROUPS: Record<
client: { label: 'Client', color: 'amber', icon: 'layout' },
defer: { label: 'Defer', color: 'amber', icon: 'pointer-click' },
measure: { label: 'Measure', color: 'gray', icon: 'timer' },
silence: { label: 'Silence', color: 'red', icon: 'octagon' },
render: { label: 'Render', color: 'gray', icon: 'layout' },
}

export type FixCard = {
Expand Down Expand Up @@ -146,6 +150,47 @@ const dynamicCards: FixCard[] = [
},
]

// ── Unrendered-segment cards ──────────────────────

const unrenderedSegmentCards: FixCard[] = [
{
id: 'render-the-dropped-segment',
title: 'Render the dropped segment',
group: 'render',
link: 'https://nextjs.org/docs/messages/unrendered-instant-segment#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: '}' },
],
},
{
id: 'skip-validation-on-the-segment',
title: 'Skip validation on the segment',
group: 'silence',
link: 'https://nextjs.org/docs/messages/unrendered-instant-segment#skip-validation-on-the-segment',
snippets: [
{ text: '// page.tsx or layout.tsx' },
{ text: '' },
{ text: 'export const instant = false', highlight: true },
],
},
]

// ── Metadata cards ────────────────────────────────

const metadataRuntimeCards: FixCard[] = [
Expand Down Expand Up @@ -485,15 +530,18 @@ export type GuidanceKind =
| 'viewport'
| 'sync-io'
| 'sync-io-client'
| 'unrendered-segment'

export type GuidanceVariant = 'runtime' | 'navigation'
export type GuidanceVariant = 'runtime' | 'dynamic'

export const DOCS_URLS: Record<GuidanceKind, string> = {
'blocking-route': 'https://nextjs.org/docs/messages/blocking-route',
metadata: 'https://nextjs.org/docs/messages/next-prerender-dynamic-metadata',
viewport: 'https://nextjs.org/docs/messages/next-prerender-dynamic-viewport',
'sync-io': '',
'sync-io-client': '',
'unrendered-segment':
'https://nextjs.org/docs/messages/unrendered-instant-segment',
}

export const SYNC_IO_DOCS: Record<string, string> = {
Expand Down Expand Up @@ -560,8 +608,13 @@ export const EXPLANATIONS: Record<GuidanceKind, string> = {
'sync-io': '',
'sync-io-client':
'This value would be evaluated during the prerender and fixed at build time, instead of recomputed on each visit.',
'unrendered-segment':
'This segment was dropped from rendering. Issues that would prevent instant navigation will go undetected.',
}

export const BLOCKING_ROUTE_NAVIGATION_EXPLANATION =
'This prevents the navigation from being instant, leading to a slower user experience.'

const syncCardsByCause: Record<string, FixCard[]> = {
'Math.random()': syncMathCards,
'Date.now()': syncDateCards,
Expand Down Expand Up @@ -601,7 +654,7 @@ export function getCards(
): FixCard[] {
switch (kind) {
case 'blocking-route':
return variant === 'navigation' ? dynamicCards : runtimeCards
return variant === 'dynamic' ? dynamicCards : runtimeCards
case 'metadata':
return variant === 'runtime' ? metadataRuntimeCards : metadataDynamicCards
case 'viewport':
Expand All @@ -610,6 +663,8 @@ export function getCards(
return (cause && syncCardsByCause[cause]) || []
case 'sync-io-client':
return (cause && syncClientCardsByCause[cause]) || []
case 'unrendered-segment':
return unrenderedSegmentCards
default:
return kind satisfies never
}
Expand Down
Loading
Loading