WCAG 2.2 AA compliance, ARIA patterns, keyboard navigation, screen reader optimization
日本語の概要は準備中です。原文の説明を表示しています。
React error boundary hierarchy, fallback UI patterns, offline-first fallback, retry mechanisms, and graceful degradation.
インストール方法を見るインストールする前に、エージェントに与えられる指示の中身を確認できます。
React error boundary hierarchy and graceful degradation patterns.
// Class-based (React requirement for componentDidCatch)
interface ErrorBoundaryProps {
children: React.ReactNode
fallback?: React.ComponentType<FallbackProps>
onError?: (error: Error, info: React.ErrorInfo) => void
}
interface ErrorBoundaryState {
error: Error | null
}
export interface FallbackProps {
error: Error
reset: () => void
}
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { error: null }
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { error }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('[ErrorBoundary]', error, info.componentStack)
this.props.onError?.(error, info)
}
reset = () => this.setState({ error: null })
render() {
if (this.state.error) {
const Fallback = this.props.fallback ?? DefaultFallback
return <Fallback error={this.state.error} reset={this.reset} />
}
return this.props.children
}
}
// react-error-boundary library (recommended — battle-tested)
// npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary'
<ErrorBoundary
FallbackComponent={MyFallback}
onError={(error, info) => reportToSentry(error, info)}
onReset={() => queryClient.clear()}
>
<App />
</ErrorBoundary>
App (top-level — catches everything, shows full-page error)
├── Navigation (no boundary — nav errors bubble to app)
├── <ErrorBoundary fallback={PageError}> ← page level
│ └── DashboardPage
│ ├── <ErrorBoundary fallback={SectionError}> ← section level
│ │ └── StatsSection
│ └── <ErrorBoundary fallback={WidgetError}> ← widget level
│ └── ChartWidget (one fails, others work)
└── Sidebar (separate boundary — sidebar error ≠ main content error)
// Page-level boundary (reset on navigation)
import { usePathname } from 'next/navigation'
import { ErrorBoundary } from 'react-error-boundary'
export function PageErrorBoundary({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
return (
<ErrorBoundary
key={pathname} // reset boundary on route change
FallbackComponent={PageErrorFallback}
onError={reportError}
>
{children}
</ErrorBoundary>
)
}
// Widget-level boundary (isolated, inline fallback)
export function WidgetErrorBoundary({ children, name }: { children: React.ReactNode; name: string }) {
return (
<ErrorBoundary
fallback={<WidgetErrorFallback name={name} />}
onError={(err) => reportError(err, { widget: name })}
>
{children}
</ErrorBoundary>
)
}
// Full-page fallback (app boundary)
function PageErrorFallback({ error, reset }: FallbackProps) {
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-4 p-8 text-center">
<h1 className="text-2xl font-bold text-gray-900">Something went wrong</h1>
<p className="max-w-md text-gray-500">
{process.env.NODE_ENV === 'development'
? error.message
: 'An unexpected error occurred. Our team has been notified.'}
</p>
<div className="flex gap-3">
<button onClick={reset} className="btn btn-primary">Try again</button>
<a href="/" className="btn btn-outline">Go home</a>
</div>
{process.env.NODE_ENV === 'development' && (
<pre className="mt-4 max-w-2xl overflow-auto rounded bg-red-50 p-4 text-left text-sm text-red-800">
{error.stack}
</pre>
)}
</div>
)
}
// Inline section fallback (section boundary)
function SectionErrorFallback({ error, reset }: FallbackProps) {
return (
<div className="rounded-lg border border-red-200 bg-red-50 p-6 text-center">
<p className="text-red-700 font-medium">Failed to load this section</p>
<button onClick={reset} className="mt-3 text-sm text-red-600 underline hover:no-underline">
Retry
</button>
</div>
)
}
// Toast-style fallback (non-critical widget)
function WidgetErrorFallback({ name }: { name: string }) {
return (
<div className="flex items-center gap-2 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-700">
<span>⚠</span>
<span>{name} unavailable</span>
</div>
)
}
import { ErrorBoundary } from 'react-error-boundary'
import { useState, useCallback } from 'react'
function RetryFallback({ error, reset }: FallbackProps) {
const [retries, setRetries] = useState(0)
const maxRetries = 3
const handleRetry = useCallback(() => {
if (retries < maxRetries) {
setRetries(r => r + 1)
reset()
}
}, [retries, reset])
return (
<div className="rounded-lg border bg-white p-6 text-center">
<p className="font-medium text-gray-900">Failed to load</p>
{retries < maxRetries ? (
<button onClick={handleRetry} className="mt-4 btn btn-sm">
Retry ({maxRetries - retries} left)
</button>
) : (
<p className="mt-4 text-sm text-gray-400">
Please refresh the page or <a href="/support" className="underline">contact support</a>.
</p>
)}
</div>
)
}
import * as Sentry from '@sentry/nextjs'
export function reportError(
error: Error,
context?: Record<string, unknown>,
info?: React.ErrorInfo
) {
if (process.env.NODE_ENV === 'development') {
console.error('[Error]', error, context)
return
}
Sentry.withScope(scope => {
if (context) scope.setExtras(context)
if (info?.componentStack) scope.setExtra('componentStack', info.componentStack)
Sentry.captureException(error)
})
}
'use client'
import { useState, useEffect } from 'react'
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(
typeof window !== 'undefined' ? navigator.onLine : true
)
useEffect(() => {
const on = () => setIsOnline(true)
const off = () => setIsOnline(false)
window.addEventListener('online', on)
window.addEventListener('offline', off)
return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off) }
}, [])
return isOnline
}
export function OfflineBanner() {
const isOnline = useOnlineStatus()
if (isOnline) return null
return (
<div role="status" className="fixed top-0 inset-x-0 z-50 bg-amber-500 py-2 text-center text-sm font-medium text-white">
You are offline. Changes will sync when connection is restored.
</div>
)
}
// Multiple independent widgets — one fails, others keep working
export function Dashboard() {
return (
<div className="grid grid-cols-2 gap-4">
<WidgetErrorBoundary name="Revenue Chart"><RevenueChart /></WidgetErrorBoundary>
<WidgetErrorBoundary name="User Stats"><UserStats /></WidgetErrorBoundary>
<WidgetErrorBoundary name="Activity Feed"><ActivityFeed /></WidgetErrorBoundary>
<WidgetErrorBoundary name="Notifications"><NotificationPanel /></WidgetErrorBoundary>
</div>
)
}
// Graceful degradation: show stale data when fetch fails
async function StatsWidget() {
try {
const stats = await fetchStats()
return <StatsDisplay stats={stats} fresh />
} catch {
const cached = await getCachedStats()
if (cached) return <StatsDisplay stats={cached} stale />
throw new Error('Stats unavailable') // let error boundary handle
}
}
import { usePathname } from 'next/navigation'
import { useEffect, useRef } from 'react'
import { useErrorBoundary } from 'react-error-boundary'
function NavigationErrorReset() {
const pathname = usePathname()
const { resetBoundary } = useErrorBoundary()
const prevPath = useRef(pathname)
useEffect(() => {
if (prevPath.current !== pathname) {
prevPath.current = pathname
resetBoundary()
}
}, [pathname, resetBoundary])
return null
}
// Place inside ErrorBoundary to auto-reset on navigation
<ErrorBoundary FallbackComponent={ErrorFallback}>
<NavigationErrorReset />
{children}
</ErrorBoundary>
function ErrorFallback({ error, reset }: FallbackProps) {
const isDev = process.env.NODE_ENV === 'development'
return (
<div role="alert" className="rounded-lg border border-red-200 bg-red-50 p-6">
<h2 className="font-semibold text-red-900">
{isDev ? `Error: ${error.name}` : 'Something went wrong'}
</h2>
{isDev ? (
<details className="mt-3">
<summary className="cursor-pointer text-sm text-red-700">Stack trace</summary>
<pre className="mt-2 overflow-auto text-xs text-red-800 whitespace-pre-wrap">{error.stack}</pre>
</details>
) : (
<p className="mt-2 text-sm text-red-700">An unexpected error occurred. Please try again.</p>
)}
<button onClick={reset} className="mt-4 text-sm font-medium text-red-700 underline">Try again</button>
</div>
)
}
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ErrorBoundary } from 'react-error-boundary'
function BrokenComponent({ shouldThrow }: { shouldThrow: boolean }) {
if (shouldThrow) throw new Error('Test error')
return <div>Working fine</div>
}
describe('ErrorBoundary', () => {
beforeEach(() => vi.spyOn(console, 'error').mockImplementation(() => {}))
afterEach(() => vi.mocked(console.error).mockRestore())
it('shows fallback UI when child throws', () => {
render(
<ErrorBoundary FallbackComponent={ErrorFallback}>
<BrokenComponent shouldThrow />
</ErrorBoundary>
)
expect(screen.getByRole('alert')).toBeInTheDocument()
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument()
})
it('resets and shows content after retry', async () => {
const user = userEvent.setup()
let shouldThrow = true
function Toggle() {
if (shouldThrow) throw new Error('Test error')
return <div>Recovered</div>
}
render(<ErrorBoundary FallbackComponent={ErrorFallback}><Toggle /></ErrorBoundary>)
shouldThrow = false
await user.click(screen.getByRole('button', { name: /try again/i }))
expect(screen.getByText('Recovered')).toBeInTheDocument()
})
})
| Scope | Fallback Type | When |
|---|---|---|
| Widget | Inline placeholder | Non-critical UI section |
| Section | Collapsed + retry button | Independent feature area |
| Page | Full page with retry | Page-level data failure |
| App | Full page with refresh | Unrecoverable state |
| Network | Toast + offline banner | Connectivity issues |
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert" className="p-4 bg-red-50 border border-red-200 rounded">
<h2 className="text-lg font-semibold text-red-800">Something went wrong</h2>
<pre className="mt-2 text-sm text-red-600">{error.message}</pre>
<button
onClick={resetErrorBoundary}
className="mt-3 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
Try again
</button>
</div>
);
}
// Usage
<ErrorBoundary FallbackComponent={ErrorFallback}>
<RiskyComponent />
</ErrorBoundary>
App Error Boundary (full page fallback)
├── Layout Error Boundary (layout-level recovery)
│ ├── Sidebar Error Boundary (sidebar collapses gracefully)
│ └── Main Content Error Boundary
│ ├── Widget A Error Boundary (individual widget fails)
│ ├── Widget B Error Boundary
│ └── Widget C Error Boundary
// App-level: full page fallback with navigation
<ErrorBoundary FallbackComponent={FullPageError}>
<Layout>
{/* Section-level: isolated failures */}
<ErrorBoundary FallbackComponent={SectionError}>
<Dashboard />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={SectionError}>
<Analytics />
</ErrorBoundary>
</Layout>
</ErrorBoundary>
// app/dashboard/error.tsx
'use client';
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log to error reporting service
reportError(error);
}, [error]);
return (
<div className="flex flex-col items-center justify-center min-h-[400px]">
<h2>Dashboard could not load</h2>
<p className="text-gray-500">{error.message}</p>
<button onClick={reset} className="mt-4 btn-primary">
Retry
</button>
</div>
);
}
// 1. Inline fallback (widget level)
function InlineFallback({ error }: { error: Error }) {
return (
<div className="p-3 text-sm text-gray-500 bg-gray-50 rounded">
Unable to load this section
</div>
);
}
// 2. Toast notification (non-critical)
function ToastFallback({ error, resetErrorBoundary }: FallbackProps) {
useEffect(() => {
toast.error(error.message, {
action: { label: 'Retry', onClick: resetErrorBoundary },
});
}, [error]);
return null; // renders nothing, shows toast instead
}
// 3. Full page (critical)
function FullPageFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h1 className="text-2xl font-bold">Something went wrong</h1>
<p className="mt-2 text-gray-600">Please try refreshing the page</p>
<div className="mt-4 space-x-3">
<button onClick={resetErrorBoundary}>Retry</button>
<button onClick={() => window.location.reload()}>Refresh</button>
</div>
</div>
);
}
import { ErrorBoundary } from 'react-error-boundary';
import { QueryErrorResetBoundary } from '@tanstack/react-query';
// Auto-reset on navigation
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// Clear any cached state that caused the error
queryClient.invalidateQueries();
}}
resetKeys={[pathname]} // auto-reset when URL changes
>
<Outlet />
</ErrorBoundary>
// With TanStack Query
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
<SuspenseComponent />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
function logError(error: Error, info: { componentStack?: string }) {
// Send to Sentry/monitoring
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'production') {
Sentry.captureException(error, {
extra: { componentStack: info.componentStack },
});
}
}
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={logError}
>
<App />
</ErrorBoundary>
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(
typeof navigator !== 'undefined' ? navigator.onLine : true
);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// Offline banner
function OfflineBanner() {
const isOnline = useOnlineStatus();
if (isOnline) return null;
return (
<div className="fixed top-0 inset-x-0 bg-yellow-500 text-center py-2 z-50">
You are offline. Some features may be unavailable.
</div>
);
}
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function ThrowError() {
throw new Error('Test error');
}
test('renders fallback on error', () => {
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
render(
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ThrowError />
</ErrorBoundary>
);
expect(screen.getByRole('alert')).toBeInTheDocument();
expect(screen.getByText(/test error/i)).toBeInTheDocument();
spy.mockRestore();
});
test('resets on retry click', async () => {
let shouldThrow = true;
function MaybeThrow() {
if (shouldThrow) throw new Error('Boom');
return <div>Recovered</div>;
}
render(
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MaybeThrow />
</ErrorBoundary>
);
shouldThrow = false;
await userEvent.click(screen.getByText('Try again'));
expect(screen.getByText('Recovered')).toBeInTheDocument();
});
| Scope | Fallback Type | When |
|---|---|---|
| Widget | Inline placeholder | Non-critical UI section |
| Section | Collapsed + retry button | Independent feature area |
| Page | Full page with retry | Page-level data failure |
| App | Full page with refresh | Unrecoverable state |
| Network | Toast + offline banner | Connectivity issues |
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
WCAG 2.2 AA compliance, ARIA patterns, keyboard navigation, screen reader optimization
日本語の概要は準備中です。原文の説明を表示しています。
axe-core integration, WCAG 2.2 AA checklist, keyboard navigation testing, screen reader testing, and ARIA pattern validation.
日本語の概要は準備中です。原文の説明を表示しています。
Steam-style achievement system with XP, levels, streaks, and skill trees. Gamifies the development workflow. 25 achievements across 5 categories.
日本語の概要は準備中です。原文の説明を表示しています。
Framework for measuring and tracking agent response quality over time. Detects regressions before they reach production. Use when evaluating agent changes, auditing quality, or establishing performance baselines.
日本語の概要は準備中です。原文の説明を表示しています。
Agent Context Isolation
日本語の概要は準備中です。原文の説明を表示しています。
Agent ve skill dosyalarinin yapisal dogrulamasi. Frontmatter kontrol, naming convention, zorunlu bolum kontrolu, tutarlilik denetimi. Yeni agent/skill eklendiginde veya mevcut dosyalar duzenlediginde otomatik calistirilir.
日本語の概要は準備中です。原文の説明を表示しています。