Overview
Request to add an ErrorBoundary component to Kumo. The CONTRIBUTING.md guidance suggests opening an issue first for non-trivial additions, so this is a proposal: please push back on shape / scope / whether this belongs in Kumo at all before any code lands.
Motivation
Every app that consumes Kumo ends up writing its own error boundary, because React doesn't ship one and Kumo doesn't either. They tend to look very similar:
- A class component (still the only way to do
getDerivedStateFromError / componentDidCatch).
- A default fallback UI with a warning icon, a title, the error message in a
<pre>, and a "Try again" button.
- A
name prop so the caller can label which boundary tripped, both for the fallback heading and for the console log.
- Optional render-prop fallback for callers who want their own UI:
fallback?: (error, reset) => ReactNode.
The bit that differs in production-grade implementations is error classification + a smarter reset behaviour:
- Hook-order violations (
Rendered fewer hooks than expected, Rules of Hooks) - log a specific Rules-of-Hooks pointer in addition to the regular stack, because retrying re-renders the same broken code path. These are always bugs, not transient failures.
- Chunk-loading failures (
Failed to fetch dynamically imported module, Loading chunk N failed, Vite-HMR MIME mismatch errors) - the broken module will keep throwing on retry, so "Try again" should force a full page reload (window.location.reload()) instead of clearing the error state.
- Generic render errors - the default "Try again" that clears state and re-renders the subtree.
I have a working implementation of all of the above in an internal Cloudflare app. It's ~170 lines including the fallback UI and error-classifier regexes. Happy to port it to Kumo's conventions (compound component pattern if appropriate, semantic color tokens instead of raw Tailwind, cn(), displayName, JSDoc, KUMO_*_VARIANTS, etc.).
Proposed API
import { ErrorBoundary } from "@cloudflare/kumo";
// Default fallback UI (warning icon + title + error message + Try again button)
<ErrorBoundary name="Sidebar">
<AppSidebar />
</ErrorBoundary>
// Custom fallback via render prop
<ErrorBoundary
name="Dashboard"
fallback={(error, reset) => (
<div>
<p>{error.message}</p>
<Button onClick={reset}>Retry</Button>
</div>
)}
>
<Dashboard />
</ErrorBoundary>
Props
children: ReactNode - required, the subtree to wrap.
name?: string - optional label, shown in the fallback heading (Something went wrong in {name}) and prefixed to console logs. Helps diagnose which boundary tripped when several are nested.
fallback?: (error: Error, reset: () => void) => ReactNode - optional render prop. When provided, fully replaces the default fallback UI.
onError?: (error: Error, info: ErrorInfo) => void - optional callback for telemetry pipelines (Sentry, Datadog, etc.). Fires from componentDidCatch.
Behaviour
- Default fallback uses
role="alert" so screen readers announce errors when they appear.
- Error classification runs against
error.message to bucket into hooks | chunk | generic. The bucket is used to:
- Tailor the fallback title and message (hook violations get a Rules-of-Hooks pointer).
- Decide whether
reset() clears state (generic / hooks) or reloads the page (chunk).
- Console logging is always-on, even in production (errors caught by boundaries are real bugs, not noise; suppressing them in prod hurts debugging when you're staring at a Sentry alert and want the stack trace in the user's DevTools).
Why this fits Kumo
- It's a primitive that every consuming app needs.
- The default fallback styling (border, padding, typography, button) should match Kumo's design language - which means the right place for it is in Kumo, not duplicated by every consumer.
- It's framework-agnostic (no React Router / Next.js dependency), so it lives well inside
@cloudflare/kumo.
Why this might NOT belong in Kumo
A few reasons to push back, and how I'd respond:
- "It's not a UI primitive, it's app infrastructure." The render-prop API +
onError + name shape is infrastructure-flavoured, fair. But the default fallback's visual identity is purely Kumo's job, and bundling the two avoids each consumer reinventing the styling.
- "react-error-boundary already exists." It does, and it's good. But (a) it doesn't ship classification, and (b) it doesn't ship a Kumo-styled fallback. We could depend on it under the hood and provide the styled fallback + classifier on top, which would shrink the maintenance surface inside Kumo. Happy to take that direction if maintainers prefer.
- "Putting Kumo's design language on an error fallback is a footgun for prod." Real concern. Counter: the alternative is each consumer building their own, which is what causes the inconsistency this issue is trying to fix.
What I'd like from this issue
- Yes / no on the principle: should this live in Kumo at all? If no, close and I'll keep the implementation app-side.
- API shape feedback: are the four props the right surface? Should
onError be there from day one or added later? Should fallback get errorKind (the classification result) as a third arg so custom fallbacks can also branch on it?
- Implementation direction: roll our own ~170-line class component, or depend on
react-error-boundary and layer the classifier + styled fallback on top?
Once the shape is settled, I'll open a PR.
Reference implementation
From the internal reference implementation. The classification regexes:
const HOOKS_PATTERN =
/rendered (more|fewer) hooks|change in the order of hooks|hooks can only be called/i;
const CHUNK_PATTERN =
/requested module.*MIME|failed to fetch dynamically imported|loading chunk|dynamically imported module/i;
Reset behaviour:
reset = () => {
if (this.state.errorKind === "chunk") {
window.location.reload();
return;
}
this.setState({ hasError: false, error: null, errorKind: null });
};
I'd port the regex-driven classifier as-is, and we can refine the patterns over time as new error shapes show up. The classification doesn't need to be perfect - the buckets are about reset semantics (retry vs reload), and "generic" is a fine fallback when nothing matches.
Overview
Request to add an
ErrorBoundarycomponent to Kumo. The CONTRIBUTING.md guidance suggests opening an issue first for non-trivial additions, so this is a proposal: please push back on shape / scope / whether this belongs in Kumo at all before any code lands.Motivation
Every app that consumes Kumo ends up writing its own error boundary, because React doesn't ship one and Kumo doesn't either. They tend to look very similar:
getDerivedStateFromError/componentDidCatch).<pre>, and a "Try again" button.nameprop so the caller can label which boundary tripped, both for the fallback heading and for the console log.fallback?: (error, reset) => ReactNode.The bit that differs in production-grade implementations is error classification + a smarter reset behaviour:
Rendered fewer hooks than expected,Rules of Hooks) - log a specific Rules-of-Hooks pointer in addition to the regular stack, because retrying re-renders the same broken code path. These are always bugs, not transient failures.Failed to fetch dynamically imported module,Loading chunk N failed, Vite-HMR MIME mismatch errors) - the broken module will keep throwing on retry, so "Try again" should force a full page reload (window.location.reload()) instead of clearing the error state.I have a working implementation of all of the above in an internal Cloudflare app. It's ~170 lines including the fallback UI and error-classifier regexes. Happy to port it to Kumo's conventions (compound component pattern if appropriate, semantic color tokens instead of raw Tailwind,
cn(), displayName, JSDoc, KUMO_*_VARIANTS, etc.).Proposed API
Props
children: ReactNode- required, the subtree to wrap.name?: string- optional label, shown in the fallback heading (Something went wrong in {name}) and prefixed to console logs. Helps diagnose which boundary tripped when several are nested.fallback?: (error: Error, reset: () => void) => ReactNode- optional render prop. When provided, fully replaces the default fallback UI.onError?: (error: Error, info: ErrorInfo) => void- optional callback for telemetry pipelines (Sentry, Datadog, etc.). Fires fromcomponentDidCatch.Behaviour
role="alert"so screen readers announce errors when they appear.error.messageto bucket intohooks | chunk | generic. The bucket is used to:reset()clears state (generic / hooks) or reloads the page (chunk).Why this fits Kumo
@cloudflare/kumo.Why this might NOT belong in Kumo
A few reasons to push back, and how I'd respond:
onError+nameshape is infrastructure-flavoured, fair. But the default fallback's visual identity is purely Kumo's job, and bundling the two avoids each consumer reinventing the styling.What I'd like from this issue
onErrorbe there from day one or added later? ShouldfallbackgeterrorKind(the classification result) as a third arg so custom fallbacks can also branch on it?react-error-boundaryand layer the classifier + styled fallback on top?Once the shape is settled, I'll open a PR.
Reference implementation
From the internal reference implementation. The classification regexes:
Reset behaviour:
I'd port the regex-driven classifier as-is, and we can refine the patterns over time as new error shapes show up. The classification doesn't need to be perfect - the buckets are about reset semantics (retry vs reload), and "generic" is a fine fallback when nothing matches.