You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a 'use cache' recorded an invalid dynamic usage error on the work store (e.g. a cookies() call inside 'use cache', a nested-dynamic cacheLife, or a 'use cache' fill timeout), renderToHTMLOrFlight used to throw the recorded error right after renderToStream returned. That throw bubbled up through base-server and rendered the Pages-Router /_error page in an App Router context.
This change removes the throw so the error reaches the dev overlay through the same Flight channel that already surfaces static-shell-validation and instant-validation errors — logMessagesAndSendErrorsToBrowser, called from spawnStaticShellValidationInDev and from the validation-skipped fallback in generateDynamicFlightRenderResultWithStagesInDev.
The original motivation for the throw was to avoid double-logging in the uncaught case. Without it, both React's serverComponentsErrorHandler (which stamps a digest and emits a Flight error chunk) and logMessagesAndSendErrorsToBrowser would forward the same error. The dedup signal is now whether the recorded error already carries a digest — if so, React has already seen it and logMessagesAndSendErrorsToBrowser is skipped. Caught cases (no digest) surface through logMessagesAndSendErrorsToBrowser as a collapsed dev-overlay entry; uncaught cases surface via React's Flight error chunk as an auto-opened redbox.
This also sets up the upstack PR that attaches the inner cache call site as cause of the nested-dynamic prerender error (#1194) — Flight preserves cause natively, so the cause flows to the dev overlay without needing to serialize it into the error page.
packages/next/src/server/app-render/app-render.tsx: remove the post-renderToStream throw of the recorded work-store dynamic-usage error. Route the error through Flight + logMessagesAndSendErrorsToBrowser, dedup-skipping when the error already carries a digest.
packages/next/src/server/use-cache/use-cache-errors.ts: drop ~37 lines (the now-unused error-throwing helper).
packages/next/src/server/use-cache/use-cache-wrapper.ts: small adjustment to recorded-error flow.
Tests in cache-components-errors, use-cache-* exercise the new Flight-based surfacing path.
Impact on vinext
This is the second half of the App Router dev error-surfacing story — the first half (#958) made client-side navigations forward recorded errors. This PR changes the protocol: errors travel over Flight instead of being thrown server-side and rendered through the Pages /_error page.
For vinext's App Router dev pipeline:
Throwing the recorded dynamic-usage error after renderToStream returns will hit vinext's Pages-Router error handling in an App Router context, which is the wrong UX (matches Next.js' previous bug).
The dev overlay surfacing logic must dedup against React's Flight error chunk by checking whether the recorded error has a digest. Without this dedup, users will see the same error twice (once from React's serverComponentsErrorHandler emitting a Flight chunk, once from vinext's own forwarder).
Audit vinext's App Router dev render pipeline (entries/app-rsc-entry.ts and dev SSR path) for any post-renderToStream throw of work-store-recorded errors. Remove it; let the error flow over Flight.
In vinext's dev forwarder (the equivalent of logMessagesAndSendErrorsToBrowser), skip forwarding when the recorded error already has a digest — React has already stamped it and emitted a Flight error chunk.
Verify the auto-opened redbox path for uncaught dynamic-usage errors (cookies() inside 'use cache', nested-dynamic cacheLife, fill timeout) shows React's Flight error chunk, not vinext's /_error route.
Verify caught cases (user-try/catch around the cache call) still appear as a collapsed dev overlay entry via the forwarder.
Add an e2e/dev test for an uncaught cookies()-inside-'use cache' that asserts an auto-opened redbox (not an /_error page render) and a single overlay entry (not duplicates).
Next.js Change
When a
'use cache'recorded an invalid dynamic usage error on the work store (e.g. acookies()call inside'use cache', a nested-dynamiccacheLife, or a'use cache'fill timeout),renderToHTMLOrFlightused to throw the recorded error right afterrenderToStreamreturned. That throw bubbled up throughbase-serverand rendered the Pages-Router/_errorpage in an App Router context.This change removes the throw so the error reaches the dev overlay through the same Flight channel that already surfaces static-shell-validation and instant-validation errors —
logMessagesAndSendErrorsToBrowser, called fromspawnStaticShellValidationInDevand from the validation-skipped fallback ingenerateDynamicFlightRenderResultWithStagesInDev.The original motivation for the throw was to avoid double-logging in the uncaught case. Without it, both React's
serverComponentsErrorHandler(which stamps a digest and emits a Flight error chunk) andlogMessagesAndSendErrorsToBrowserwould forward the same error. The dedup signal is now whether the recorded error already carries adigest— if so, React has already seen it andlogMessagesAndSendErrorsToBrowseris skipped. Caught cases (nodigest) surface throughlogMessagesAndSendErrorsToBrowseras a collapsed dev-overlay entry; uncaught cases surface via React's Flight error chunk as an auto-opened redbox.This also sets up the upstack PR that attaches the inner cache call site as
causeof the nested-dynamic prerender error (#1194) — Flight preservescausenatively, so the cause flows to the dev overlay without needing to serialize it into the error page.Commit:
f9278fdPR: #93706
What Changed
packages/next/src/server/app-render/app-render.tsx: remove the post-renderToStreamthrow of the recorded work-store dynamic-usage error. Route the error through Flight +logMessagesAndSendErrorsToBrowser, dedup-skipping when the error already carries adigest.packages/next/src/server/use-cache/use-cache-errors.ts: drop ~37 lines (the now-unused error-throwing helper).packages/next/src/server/use-cache/use-cache-wrapper.ts: small adjustment to recorded-error flow.cache-components-errors,use-cache-*exercise the new Flight-based surfacing path.Impact on vinext
This is the second half of the App Router dev error-surfacing story — the first half (#958) made client-side navigations forward recorded errors. This PR changes the protocol: errors travel over Flight instead of being thrown server-side and rendered through the Pages
/_errorpage.For vinext's App Router dev pipeline:
renderToStreamreturns will hit vinext's Pages-Router error handling in an App Router context, which is the wrong UX (matches Next.js' previous bug).digest. Without this dedup, users will see the same error twice (once from React'sserverComponentsErrorHandleremitting a Flight chunk, once from vinext's own forwarder).causechain from Attach inner"use cache"call site ascauseof nested-dynamic prerender error #1194 (inner"use cache"call site) propagates to the dev overlay for free — Flight preservescause.Suggested Action
entries/app-rsc-entry.tsand dev SSR path) for any post-renderToStreamthrow of work-store-recorded errors. Remove it; let the error flow over Flight.logMessagesAndSendErrorsToBrowser), skip forwarding when the recorded error already has adigest— React has already stamped it and emitted a Flight error chunk.cookies()inside'use cache', nested-dynamiccacheLife, fill timeout) shows React's Flight error chunk, not vinext's/_errorroute.try/catcharound the cache call) still appear as a collapsed dev overlay entry via the forwarder.cookies()-inside-'use cache'that asserts an auto-opened redbox (not an/_errorpage render) and a single overlay entry (not duplicates).