Polish instant fix cards and validation messages#93894
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.
Tests PassedCommit: 96f7844 |
…/revamp-fix-cards # Conflicts: # packages/next/src/next-devtools/dev-overlay/container/errors.tsx # test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts # test/e2e/app-dir/instant-validation-level-error/instant-validation-level-error.test.ts # test/e2e/app-dir/instant-validation-level-manual-error/instant-validation-level-manual-error.test.ts # test/e2e/app-dir/instant-validation-level-manual-warning/instant-validation-level-manual-warning.test.ts # test/e2e/app-dir/instant-validation-level-warning/instant-validation-level-warning.test.ts # test/e2e/app-dir/instant-validation/instant-validation-parallel-slots.test.ts # test/e2e/app-dir/instant-validation/instant-validation.test.ts
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 (13 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsDiff too large to display app-page-exp..time.prod.jsfailed to diffapp-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 server.runtime.prod.jsDiff too large to display use-cache-pr..ntime.dev.jsDiff too large to display use-cache-pr..ntime.dev.jsDiff too large to display use-cache-pr..ntime.dev.jsDiff too large to display use-cache-pr..ntime.dev.jsDiff too large to display 📎 Tarball URLCommit: 96f7844 |
There was a problem hiding this comment.
Pull request overview
Polishes the Instant Validation developer experience by updating overlay fix cards and improving the wording/consistency of Instant/CLI validation messages (especially around prerendering and sync-IO), along with updating snapshots/error codes to match.
Changes:
- Refined blocking-route + sync-IO error copy (e.g. “during prerendering”, “unstable value …”) and aligned fix bullets with updated guidance.
- Updated Instant overlay fix card data + styling (card hover treatment, snippet border behavior), added a new “loading” icon, and stabilized dialog scrollbar layout.
- Refreshed E2E / production test snapshots and updated
errors.jsonwith the new message templates / codes.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/production/app-dir/build-output-prerender/build-output-prerender.test.ts | Updates build/prerender CLI snapshot expectations for revised sync-IO wording. |
| test/e2e/app-dir/instant-validation/instant-validation.test.ts | Updates Instant validation error code + message snapshots to match new copy. |
| test/e2e/app-dir/instant-validation/instant-validation-parallel-slots.test.ts | Refreshes parallel-slots Instant validation snapshots for updated messages/codes. |
| test/e2e/app-dir/instant-validation-static-shells/instant-validation-static-shells.test.ts | Adjusts “initial render” → “prerendering” wording assertions and codes. |
| test/e2e/app-dir/instant-validation-level-warning/instant-validation-level-warning.test.ts | Updates warning-level Instant validation snapshots for new messages/codes. |
| test/e2e/app-dir/instant-validation-level-manual-warning/instant-validation-level-manual-warning.test.ts | Updates manual-warning snapshots (page load phrasing, params guidance, codes). |
| test/e2e/app-dir/instant-validation-level-manual-error/instant-validation-level-manual-error.test.ts | Updates manual-error snapshots for prerendering phrasing + codes. |
| test/e2e/app-dir/instant-validation-level-error/instant-validation-level-error.test.ts | Updates error-level snapshots for prerendering phrasing + codes. |
| test/e2e/app-dir/instant-validation-causes/instant-validation-causes.test.ts | Updates cause snapshots to new codes. |
| test/e2e/app-dir/instant-validation-build/instant-validation-build.test.ts | Updates build Instant validation snapshot wording. |
| test/e2e/app-dir/cache-components-errors/cache-components-errors.test.ts | Refreshes cache-components error snapshots for new copy/codes (runtime/uncached/sync-IO). |
| test/development/app-dir/cache-components-dev-fallback-validation/cache-components-dev-fallback-validation.test.ts | Updates dev fallback validation snapshots for new messages/codes. |
| test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts | Updates dev overlay error snapshots for new sync-IO/uncached wording + codes. |
| test/development/app-dir/cache-components-dev-cache-scope/cache-components-dev-cache-scope.test.ts | Updates redbox description assertions for “prerendering” wording. |
| packages/next/src/server/app-render/sync-io-messages.ts | Updates sync-IO error messages (server + client) and telemetry/timing guidance bullet. |
| packages/next/src/server/app-render/blocking-route-messages.ts | Updates blocking-route error messages to “during prerendering” and revised fix bullets. |
| packages/next/src/next-devtools/dev-overlay/icons/fix-card-icons.tsx | Adds a new loading/spinner icon for fix cards. |
| packages/next/src/next-devtools/dev-overlay/container/errors.tsx | Updates overlay summaries and sync-IO header copy to match new wording. |
| packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance.tsx | Improves fix card hover/snippet styling and adds link-icon hover behavior. |
| packages/next/src/next-devtools/dev-overlay/components/instant/instant-guidance-data.ts | Renames cards/groups, removes viewport “wrap body” guidance, updates snippets to simpler client examples. |
| packages/next/src/next-devtools/dev-overlay/components/errors/dialog/dialog.tsx | Adds scrollbar-gutter: stable to reduce scrollbar flicker on error swaps. |
| packages/next/errors.json | Adds new error templates/codes for updated Instant/sync-IO messages and viewport guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
What?
Prerender params if known,Mark the route as dynamic,For telemetry, use a timing API.Wrap body in Suspensecard from viewport variants.during the initial render→during prerendering;blocking navigation→blocking the page load.the unstable value <expression>.fixed at build time.blockgroup.Demo