A dashboard that fetches user profile, billing status, and activity feed in parallel should degrade gracefully when only one source fails — the other two sections are still useful. CWE-755 (Improper Handling of Exceptional Conditions) applies when a single data source failure crashes an entire page that could have partially rendered. ISO 25010 reliability.fault-tolerance requires that failures in one subsystem do not propagate to others. Practically, a single top-level error boundary means a transient billing API timeout takes down the entire dashboard, blocking users from accessing unrelated functionality they need.
High because a single top-level boundary lets any one component failure crash an entire multi-source page, blocking access to all other independent sections.
Wrap each independent data-fetching section in its own ErrorBoundary and Suspense pair so failures are contained to the section that failed.
// dashboard/page.tsx
<div className="grid grid-cols-3 gap-4">
<ErrorBoundary fallback={<WidgetError name="Profile" />}>
<Suspense fallback={<Skeleton />}><ProfileWidget /></Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Activity" />}>
<Suspense fallback={<Skeleton />}><ActivityFeed /></Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Billing" />}>
<Suspense fallback={<Skeleton />}><BillingSummary /></Suspense>
</ErrorBoundary>
</div>
In Next.js App Router, add error.tsx and loading.tsx at sub-route segments for parallel routes to get this isolation automatically at the route level.
ID: saas-error-handling.graceful-degradation.partial-failures-dont-crash
Severity: high
What to look for: Examine page components that render multiple independent data-fetching sections or widgets. Look for: (1) whether each data-dependent section has its own error boundary rather than sharing a single top-level boundary; (2) whether React Suspense boundaries are placed at a granular level (around individual sections rather than the whole page); (3) whether a failure in one section (e.g., an activity feed widget) prevents the rest of the page from loading. In Next.js App Router, look for loading.tsx and error.tsx files at sub-route levels, not just the root. Check dashboard-style pages and pages with multiple API calls.
Pass criteria: Count all pages with multiple independent data sources and check each for section-level error isolation. Pass if the application has component-level or section-level error boundaries on pages with multiple independent data sources, such that a failure in 1 section allows the rest of the page to render. Report the count: "X of Y multi-source pages have section-level error boundaries."
Fail criteria: Fail if the entire page component is wrapped in a single top-level boundary with no granular boundaries, and a failure in any section crashes the full page. At least 1 page with 2 or more data sources must have section-level error isolation. Fail if there are no component-level boundaries anywhere in the application (only page-level or route-level).
Skip (N/A) when: The application is a simple single-purpose page with only one data source per view. Signal: all pages fetch a single data set; no dashboard-style layouts with multiple independent widgets or sections.
Detail on fail: "Dashboard page fetches user profile, recent activity, and billing status in parallel; single error boundary at the route level; any individual failure crashes the entire dashboard". Max 500 chars.
Remediation: A single top-level error boundary is better than nothing, but it's a blunt instrument. When possible, isolate failures to the section that failed.
Wrap independent sections in their own boundaries:
// dashboard/page.tsx
<div className="grid grid-cols-3 gap-4">
<ErrorBoundary fallback={<WidgetError name="Profile" />}>
<Suspense fallback={<Skeleton />}>
<ProfileWidget />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Activity" />}>
<Suspense fallback={<Skeleton />}>
<ActivityFeed />
</Suspense>
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Billing" />}>
<Suspense fallback={<Skeleton />}>
<BillingSummary />
</Suspense>
</ErrorBoundary>
</div>
This way, if the activity feed fails, the profile and billing widgets still render.