[GH-ISSUE #989] Handle falsy thrown values in error boundaries (e.g. throw undefined) #214

Closed
opened 2026-05-06 12:38:15 +02:00 by BreizhHardware · 0 comments

Originally created by @github-actions[bot] on GitHub (Apr 30, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/989

Upstream change

Next.js fixed falsy-value handling in client error boundaries in vercel/next.js#93134 (commit ea54198).

our error boundaries had a bunch of logic that set state.error = error and then checked if (state.error), which only works correctly if thrown value is truthy. it breaks if something does e.g. throw undefined. in this case, we would incorrectly think that no error occurred and render children again (instead of a fallback), which can then lead to an infinite loop if the children throw again.

the fix is to wrap the thrown value, so state.error is either null (initial/reset) or { thrownValue: ... }.

Files changed upstream

  • packages/next/src/client/components/catch-error.tsx
  • packages/next/src/client/components/error-boundary.tsx
  • packages/next/src/client/components/http-access-fallback/error-boundary.tsx
  • packages/next/src/client/components/redirect-boundary.tsx
  • packages/next/src/client/components/nav-failure-handler.ts
  • packages/next/src/next-devtools/userspace/app/app-dev-overlay-error-boundary.tsx
  • packages/next/src/next-devtools/userspace/pages/pages-dev-overlay-error-boundary.tsx

Why this matters for vinext

vinext reimplements the App Router error boundary chain (error boundary, redirect boundary, http-access-fallback boundary) for both the RSC and SSR environments. If we use the same state.error = error; if (state.error) pattern, we will also:

  1. Render children instead of the fallback UI when something throws a falsy value (throw undefined, throw null, throw 0, throw '', throw false)
  2. Risk an infinite re-render loop if those children throw again on every render

This is related to issue #810 (Don't swallow strings thrown in Server Components) but covers the broader falsy-value case in client-side error boundaries.

Action

  • Audit any vinext error-boundary code (App Router error boundary, redirect boundary, HTTP access fallback boundary, dev overlay error boundary) for the if (state.error) truthiness check
  • Switch to a discriminated union: either null (no error) or { thrownValue: unknown } (errored), matching upstream
  • Port the upstream test cases (test/e2e/app-dir/catch-error/, test/e2e/app-dir/errors/, test/e2e/app-dir/global-error/basic/) for throw null / throw undefined from both client and server components
  • Reference the new tests with a comment linking to the upstream test files

References

Originally created by @github-actions[bot] on GitHub (Apr 30, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/989 ## Upstream change Next.js fixed falsy-value handling in client error boundaries in [vercel/next.js#93134](https://github.com/vercel/next.js/pull/93134) (commit [ea54198](https://github.com/vercel/next.js/commit/ea541987d1735493c63b52d0b6e673d4435cbb20)). > our error boundaries had a bunch of logic that set `state.error = error` and then checked `if (state.error)`, which only works correctly if thrown value is truthy. it breaks if something does e.g. `throw undefined`. in this case, we would incorrectly think that no error occurred and render children again (instead of a fallback), which can then lead to an infinite loop if the children throw again. > > the fix is to wrap the thrown value, so `state.error` is either `null` (initial/reset) or `{ thrownValue: ... }`. ## Files changed upstream - `packages/next/src/client/components/catch-error.tsx` - `packages/next/src/client/components/error-boundary.tsx` - `packages/next/src/client/components/http-access-fallback/error-boundary.tsx` - `packages/next/src/client/components/redirect-boundary.tsx` - `packages/next/src/client/components/nav-failure-handler.ts` - `packages/next/src/next-devtools/userspace/app/app-dev-overlay-error-boundary.tsx` - `packages/next/src/next-devtools/userspace/pages/pages-dev-overlay-error-boundary.tsx` ## Why this matters for vinext vinext reimplements the App Router error boundary chain (error boundary, redirect boundary, http-access-fallback boundary) for both the RSC and SSR environments. If we use the same `state.error = error; if (state.error)` pattern, we will also: 1. Render `children` instead of the fallback UI when something throws a falsy value (`throw undefined`, `throw null`, `throw 0`, `throw ''`, `throw false`) 2. Risk an infinite re-render loop if those children throw again on every render This is related to issue #810 (Don't swallow strings thrown in Server Components) but covers the broader falsy-value case in client-side error boundaries. ## Action - Audit any vinext error-boundary code (App Router error boundary, redirect boundary, HTTP access fallback boundary, dev overlay error boundary) for the `if (state.error)` truthiness check - Switch to a discriminated union: either `null` (no error) or `{ thrownValue: unknown }` (errored), matching upstream - Port the upstream test cases (`test/e2e/app-dir/catch-error/`, `test/e2e/app-dir/errors/`, `test/e2e/app-dir/global-error/basic/`) for `throw null` / `throw undefined` from both client and server components - Reference the new tests with a comment linking to the upstream test files ## References - Upstream PR: https://github.com/vercel/next.js/pull/93134 - Upstream commit: https://github.com/vercel/next.js/commit/ea541987d1735493c63b52d0b6e673d4435cbb20 - Related: #810 (server component string throws)
BreizhHardware 2026-05-06 12:38:15 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#214
No description provided.