Redesign the unrendered-segment instant validation overlay#93879
Draft
aurorascharff wants to merge 41 commits into
Draft
Redesign the unrendered-segment instant validation overlay#93879aurorascharff wants to merge 41 commits into
aurorascharff wants to merge 41 commits into
Conversation
Adds two pieces of framework correctness for `unstable_instant` validation:
1. **Navigation body factories** in `blocking-route-messages.ts`:
- `createRuntimeBodyErrorInNavigation` (E1247)
- `createDynamicBodyErrorInNavigation` (E1246)
Wired into `trackDynamicHoleInNavigation` so the in-navigation case emits
its own message ("accessed under `<Suspense>`" + nav-aware "Ways to fix")
instead of reusing the initial-render copy ("accessed outside of
`<Suspense>`"). Phase-neutral phrasing ("during the initial render or a
navigation") accommodates the path firing from either prerender or
client-nav validation.
2. **Clear-on-nav reducer** in the dev overlay:
- `ACTION_INSTANT_ERRORS_CLEAR` reducer case in `shared.ts`.
- `useClearInstantErrorsOnNav` hook in `dev-overlay.tsx` watches
`usePathname()` and dispatches when the route changes.
Only errors fired during a client navigation are eligible to clear:
`*InNavigation` factories carry an `__nextInstantNav` marker, and wrapper
headlines (E1082 / E1112 / E1113 / E1118) are detected via the stable
"Could not validate `unstable_instant`" substring. Initial-render / SSR
errors lack both signals and persist, so a page that fails to prerender
stays in the overlay until the source is fixed.
Co-authored-by: Cursor <cursoragent@cursor.com>
Reads the `__nextInstantNav` marker from the error to pick the right overlay copy for the blocking-route family: - Headline: "Next.js encountered runtime/uncached data during a navigation." (in-nav) vs. "during the initial render." (initial). - Explanation: "This prevents the navigation from being instant..." vs. the default "prevents the route from being prerendered..." string. Adds `BLOCKING_ROUTE_NAVIGATION_EXPLANATION` next to `EXPLANATIONS` in `instant-guidance-data.ts` so all overlay copy stays in one place. Also renames the legacy `'navigation'` value of `GuidanceVariant` to `'dynamic'` — the original name meant "uncached data" (from #92638) and collides with the new in-navigation concept. The new shape is `'runtime' | 'dynamic'` for what kind of data is accessed, plus an orthogonal `inNavigation: boolean` for the phase. Co-authored-by: Cursor <cursoragent@cursor.com>
Object markers on the Error don't survive RSC serialization (`__NEXT_ERROR_CODE` already has a side-channel Map for this same reason), so the previous `__nextInstantNav` property was getting stripped before the dev overlay ever saw it. Switches detection to two structural substrings already present in the user-facing copy: - \`accessed under \`<Suspense>\`\` — only emitted by the in-navigation body factories. The mirrored SSR factories say \`accessed outside of \`<Suspense>\`\`, so the two are unambiguous. - \`Could not validate \`unstable_instant\`\` — already used to detect wrapper errors (E1082 / E1112 / E1113 / E1118), tied to the API name rather than phase wording. Drops the marker helper from `blocking-route-messages.ts`. Co-authored-by: Cursor <cursoragent@cursor.com>
The dev overlay renders in a separate React root mounted at the document body (via `createRoot(container)` in `dev-overlay.browser.tsx`), so the App Router's `PathnameContext` isn't in scope and `usePathname()` returns `null` — making the previous `useClearInstantErrorsOnNav` effect a no-op. Switches to a browser-level subscription: listen for `popstate` (back / forward) and patch `history.pushState` / `history.replaceState` to detect client navigations triggered by `next/link` and `router.push()`. Cleanup restores the originals so the patch is scoped to overlay lifetime. This matches the pattern other dev-overlay code already uses for current path (e.g. `route-info.tsx` reads `window.location.pathname` directly). Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…/instant-nav-error-clear
The new `createRuntimeBodyErrorInNavigation` / `createDynamicBodyErrorInNavigation` factories produce "encountered runtime data during a navigation" wording in the dev overlay (via inNavigation substring detection in errors.tsx). Seven inline snapshots in instant-validation-parallel-slots.test.ts locked in the older "during the initial render" + E1221 / E1220 wording — flipped to the new "during a navigation" + E1247 / E1246. The eighth test in this file is left at canary state: it relies on PR #93770's "expected segment was not rendered" message which currently bakes in a test/tmp/next-test-… absolute path. That test is broken on canary HEAD too (verified directly) — not a regression from this PR. Co-authored-by: Cursor <cursoragent@cursor.com>
`instant-validation.test.ts` (28 snapshots) and
`instant-validation-parallel-slots.test.ts` (7 snapshots) had build-mode
inline snapshots that pinned the older `createRuntimeBodyError` /
`createDynamicBodyError` wording. Switched to the new in-navigation
factory output:
- "encountered runtime data during the initial render."
+ "encountered runtime data during the initial render or a navigation."
- "accessed outside of `<Suspense>` prevents the route from being
prerendered, blocking navigation and leading to a slower user
experience."
+ "accessed under `<Suspense>` prevents the route from being
prerendered or the navigation from being instant, leading to a
slower user experience."
Captured without NEXT_SKIP_ISOLATE so the test packed next.js as a
tarball (matching the CI build path); avoids leaking local
`../../../packages/next/dist/esm/...` stack frames into the snapshots.
Co-authored-by: Cursor <cursoragent@cursor.com>
The in-navigation body factories said "accessed under \`<Suspense>\`", which contradicts the fix bullet that recommends wrapping in Suspense. The actual problem is the same as the SSR factories: data accessed *outside* a Suspense boundary, where the prerender / prefetchable shell can't proceed without waiting. Aligned both InNavigation factories to "accessed outside of \`<Suspense>\`" so the diagnostic matches the SSR factories — only the consequence clause differs (still includes "or the navigation from being instant"). Refactored the substring detection that distinguishes in-navigation vs SSR variants into a single `isBlockingRouteInNavError` helper in `shared.ts`, used by both `getInstantErrorRoute` (clear-on-nav) and `getBlockingRouteErrorDetails` (overlay headline phase). Helper keys off "or a navigation" which is still unique to the InNavigation factory pair (the diagnostic clause is now shared). Also narrowed `getInstantErrorRoute` to detect *only* body factory errors — wrapper errors like "Could not validate \`unstable_instant\`" are no longer cleared on navigation. They describe a validation infrastructure failure rather than a fixable route-level mistake and should stay in the redbox stack until the user addresses the cause. Snapshot updates across `instant-validation.test.ts` (dev + build modes) and `instant-validation-parallel-slots.test.ts` (dev + build modes) reflect the new wording. Build-mode snapshots captured without NEXT_SKIP_ISOLATE so the test packs Next.js as a tarball and produces the same stack frame shape CI sees (avoids leaking local dist paths). Co-authored-by: Cursor <cursoragent@cursor.com>
…nt-nav-error-clear
- Drop history.pushState/replaceState patching in favor of state.page
from ACTION_DEVTOOL_UPDATE_ROUTE_STATE (cooperates with App Router's
own history patching).
- Update errors.test.ts for new {inNavigation, variant: 'dynamic'} shape
and add InNavigation factory cases.
- Bump E1246/E1247 -> E1249/E1250 in level-* and causes tests after the
factory wording change.
Co-authored-by: Cursor <cursoragent@cursor.com>
- Restore the 13-space indent on `Unrendered segment(s):` paths in dev inline snapshots (got stripped during the earlier wording sweep). - Use the stable `app/...` prefix instead of the non-deterministic `test/tmp/next-test-<timestamp>-<rand>/...` path that crept into one parallel-slots snapshot. Co-authored-by: Cursor <cursoragent@cursor.com>
`state.page` is the resolved URL (e.g. `/foo/123`), but an instant error's embedded `Route "..."` uses the route pattern (`/foo/[param]`). Firing the clear action on the initial '' → page transition would always wipe dynamic-route errors before the redbox could open. Treat the first non-empty page as the baseline and only dispatch on subsequent changes. Co-authored-by: Cursor <cursoragent@cursor.com>
The SSR `createRuntimeBodyError` lists the Suspense placeholder first (the most general fix) followed by `generateStaticParams`. The in-navigation variant had them flipped, so the top-of-list suggestion was the narrower one (only applies to dynamic-param routes). Reorder the in-nav factory to match SSR, and refresh the build-mode CLI snapshots and the runtime-body in-nav error code (E1250 → E1251). Co-authored-by: Cursor <cursoragent@cursor.com>
The earlier wording sweep on the level-* tests preserved the pre-existing 2-space-shy indent on the `code` and `description` lines, so Jest's diff reported a whitespace-only mismatch even though the values agreed. Match them to the sibling JSON keys (13 spaces). Co-authored-by: Cursor <cursoragent@cursor.com>
The previous patch overshot to 13 spaces in tests where the snapshot braces sit two columns shallower (so sibling keys are at 11). Auto-align each `code`/`description` line to the indent of the next sibling key. Co-authored-by: Cursor <cursoragent@cursor.com>
`trackDynamicHoleInNavigation` is also reached during the build-time prerender pass, so the CLI build error now reads "during the initial render or a navigation" with the matching consequence clause. Update the build-mode snapshots in `instant-validation-build` and the four `instant-validation-level-*` tests to match. Co-authored-by: Cursor <cursoragent@cursor.com>
…ording The earlier wording sweep also touched the 4 \`without-root-suspense\` E1220 snapshots, which come from the SSR factory (initial-render path) and should keep "during the initial render." Restore the SSR wording on those four. Co-authored-by: Cursor <cursoragent@cursor.com>
…/instant-nav-error-clear
`Route "<path>":` in the error message is the route template (e.g. `/foo/[slug]`), while `state.page` is the resolved URL (e.g. `/foo/123`). Equality matching dropped SSR-streamed errors on the destination page during navigation for any dynamic route. Convert the template to a regex so the clear-on-nav reducer keeps errors whose template matches the page the user just landed on.
…rcel/next.js into aurorascharff/redesign-unrendered-segment-overlay
Contributor
Failing test suitesCommit: 774d625 | About building and testing Next.js
Expand output● hmr-deleted-page › should not show errors for a deleted page |
## Summary Adds a Storybook example for the unrendered-segment instant validation error, and keeps the small UI polish tweaks for the code snippet presentation. ## What changed - Added `InstantUnrenderedSegment` to the dev overlay error stories - Added a matching Storybook fixture for the unrendered-segment error - Added a blank line in the “Skip validation on the segment” fix-card snippet - Adjusted the code-frame gutter color to `var(--color-gray-alpha-500)` ## Why This makes the unrendered-segment instant validation state easier to review in isolation in Storybook, while also preserving the two small visual refinements requested for the snippet/code-frame presentation.
…/redesign-unrendered-segment-overlay # Conflicts: # packages/next/src/next-devtools/dev-overlay/container/errors.tsx # packages/next/src/next-devtools/dev-overlay/shared.ts # test/e2e/app-dir/instant-validation/instant-validation-parallel-slots.test.ts # test/e2e/app-dir/instant-validation/instant-validation.test.ts
Contributor
Stats from current PR✅ No significant changes detected📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (8 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsDiff too large to display app-page-exp..time.prod.jsDiff too large to display app-page-tur..ntime.dev.jsDiff too large to display app-page-tur..time.prod.jsDiff too large to display app-page-tur..ntime.dev.jsDiff too large to display app-page-tur..time.prod.jsDiff too large to display app-page.runtime.dev.jsDiff too large to display app-page.runtime.prod.jsDiff too large to display 📎 Tarball URLCommit: 774d625 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
A focused dev-overlay redesign and CLI/build message refresh for the "expected segment was not rendered" instant validation warning (PR #93770).
TODO generate again when bug with parallell route is fixed
Why?
The wrapper carries no useful source location to point at — the code-frame chrome and Call Stack the overlay rendered before were misleading. Users mostly need to know which segment didn't render and where to set
instant = falseto silence the warning.Demo
How
dynamic-rendering.ts: leads withRoute "<path>":prefix like every other validation factory, drops the verbose paragraphs in favour of a one-sentence explanation +Ways to fix this:list, and usesinstantnotunstable_instantin the fix bullet.unrendered-segmentGuidanceKindand a dedicated case inerrors.tsxthat drops the code-frame and Call Stack.UnrenderedSegmentInfocomponent renders the route + each unrendered file with the same chrome asCodeFrame, plus open-in-editor.Render the missing segment(render, gray) andAllow no validation(silence, red).