skills/error-boundary-strategist/SKILL.md
Design resilient React error handling with Error Boundaries, Sentry integration, recovery UI, retry patterns, and graceful degradation. Activate on: error boundary, crash recovery, Sentry, error fallback UI, retry logic, unhandled exceptions. NOT for: form validation errors (use form-validation-architect), API error codes (use api-architect).
npx skillsauth add curiositech/windags-skills error-boundary-strategistInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Design layered React error handling with Error Boundaries, error reporting, recovery UI, retry patterns, and graceful degradation so apps never show a white screen.
ERROR TYPE SEVERITY ACTION
───────────────────────── ──────────── ─────────────────────────
Component render error Critical Route-level boundary → full page fallback
Widget/feature crash Recoverable Feature-level boundary → inline retry
Network timeout Recoverable Query error callback → toast + retry
Chunk load failure Critical App-level boundary → full reload
Type error in handler Recoverable Try/catch → log + continue
Memory leak crash Critical Global error → browser reload
Decision Tree:
Is error from React render tree?
├── YES: Error boundary will catch it
│ └── Is error in isolated widget?
│ ├── YES: Use feature-level boundary → show inline fallback
│ └── NO: Use route-level boundary → show page fallback
└── NO: Manual handling required
└── Is error in async operation?
├── YES: Use query.onError or try/catch → show toast
└── NO: Use window.onerror → report to Sentry
If component tree depth > 3 levels:
→ Add feature-level boundaries around each major widget
If route has > 5 async data sources:
→ Add route-level boundary + individual query error handling
If app has > 10 routes:
→ Add app-level boundary as ultimate fallback
Always:
→ Add global-error.tsx in Next.js apps
Detection: componentDidCatch fires repeatedly on same component within 30 seconds
Symptom: User clicks "Try Again" → immediate re-crash → fallback shows again
Root Cause: onReset doesn't clear corrupted state that caused original error
Fix: Clear query cache, local state, and props that triggered error in onReset handler
Detection: No error boundary catches error → blank page with no UI Symptom: App shows empty white screen, no fallback UI visible Root Cause: Error boundary placed too low in tree or missing app-level catch-all Fix: Add error boundary wrapping entire app as ultimate safety net
Detection: Async errors logged to console but not caught by boundaries Symptom: Promise rejections, event handler crashes slip through boundaries Root Cause: Error boundaries only catch render-phase errors, not async Fix: Use try/catch in handlers + query.onError for async operations
Detection: Error boundary catches error but corrupted context still exists
Symptom: Reset works initially but subsequent actions crash due to bad context
Root Cause: Context provider above boundary has corrupted state
Fix: Reset context state in boundary's onReset or move boundary above context
Detection: Error fallback UI itself throws error → nested error boundaries trigger Symptom: Fallback component crashes → shows another fallback → potential infinite loop Root Cause: Complex fallback components with their own dependencies/logic Fix: Keep fallback UI extremely simple → static JSX with minimal dependencies
Scenario: Dashboard with revenue chart, recent orders, and user stats. Chart widget crashes due to malformed API data.
Expert Approach:
// Expert places boundary precisely around volatile widget
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div className="chart-error">
<p>Revenue chart temporarily unavailable</p>
<button onClick={resetErrorBoundary}>Reload Chart</button>
</div>
)}
onReset={() => {
// Only clear chart-related state
queryClient.removeQueries({ queryKey: ['revenue-chart'] });
}}
isolate // This error won't bubble up to route level
>
<RevenueChart />
</ErrorBoundary>
// Orders widget remains functional
<RecentOrders /> // No error boundary needed if stable
Novice Mistake: Places one boundary around entire dashboard → chart error kills everything Expert Insight: Isolate volatile components; stable components don't need boundaries
Testing the Setup:
// In development, inject errors to verify boundaries work
{process.env.NODE_ENV === 'development' && (
<ErrorTrigger componentName="RevenueChart" />
)}
Setup Navigation:
/dashboard route with error.tsx/dashboard/analytics with its own error.tsxError Routing Logic:
Analytics widget crashes
↓
dashboard/analytics/error.tsx catches it
↓ (if error.tsx itself crashes)
dashboard/error.tsx catches it
↓ (if that crashes too)
global-error.tsx replaces entire page
Implementation:
// app/dashboard/analytics/error.tsx
'use client';
export default function AnalyticsError({ error, reset }) {
return (
<div role="alert">
<h2>Analytics Error</h2>
<p>Unable to load analytics data</p>
<button onClick={reset}>Retry</button>
<Link href="/dashboard">Back to Dashboard</Link>
</div>
);
}
// Sentry reporting in each error boundary
useEffect(() => {
Sentry.withScope((scope) => {
scope.setTag('boundary.level', 'analytics');
scope.setContext('route', { path: '/dashboard/analytics' });
Sentry.captureException(error);
});
}, [error]);
onReset handlers clear corrupted state that caused original errorrole="alert") for accessibilitywindow.onerror and unhandledrejection handlers catch non-React errorsglobal-error.tsx as ultimate safety netThis skill should NOT be used for:
Delegation Rules:
tools
Building resilient distributed systems with circuit breakers, retries with full-jitter exponential backoff, retry budgets (per-request 3-attempt + per-client 10% ratio per Google SRE), deadline propagation, and the cascading-failure math (4 layers × 3 retries = 64x amplification). Grounded in Resilience4j, Microsoft Cloud Patterns, AWS Architecture Blog (Marc Brooker), and Google SRE Book.
testing
Designing HTTP cache headers that work correctly across browsers, CDNs, and shared proxies — `Cache-Control` directives per RFC 9111, `stale-while-revalidate` and `stale-if-error` per RFC 5861, the Vary header for varying responses, and surrogate keys for tag-based purging. Grounded in IETF RFCs and Cloudflare/Fastly docs.
development
Use when designing or fixing a Content Security Policy on a real site, choosing between nonce-based and hash-based CSP, adding strict-dynamic, debugging "Refused to execute inline script" errors, deploying CSP in report-only mode first, configuring report-to / report-uri, or auditing an existing policy for unsafe-inline / unsafe-eval / wildcards. Triggers: "CSP blocks legitimate inline script", strict-dynamic, nonce-{RANDOM}, sha256-{HASH}, object-src none, base-uri none, frame-ancestors, Trusted Types, X-Content-Security-Policy obsolete, report-only vs enforced. NOT for general HTTP security headers (HSTS, COOP/COEP), Trusted Types deep dive, CORS configuration, or building a WAF.
tools
Choosing and operating an HTTP API versioning strategy that doesn't break clients — Stripe's date-based pinned versions, the Deprecation/Sunset header pair (RFC 9745 + RFC 8594), URI vs header vs media-type approaches, and the version-transformer pattern. Grounded in Stripe's published architecture and IETF RFCs.