Skip to content

Surface invalid dynamic usage errors via Flight in dev (don't throw after renderToStream) #1195

@github-actions

Description

@github-actions

Next.js Change

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.

Commit: f9278fd
PR: #93706

What Changed

  • 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).
  • Once this lands, the cause chain from Attach inner "use cache" call site as cause of nested-dynamic prerender error #1194 (inner "use cache" call site) propagates to the dev overlay for free — Flight preserves cause.

Suggested Action

  • 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    nextjs-trackingTracking issue for a Next.js canary change relevant to vinext

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions