---
name: error-resilience-expert
description: "Expert guide for error handling patterns, resilience engineering, retry strategies, circuit breakers, and graceful degradation across React, Next.js, and Node.js / Panduan ahli pola penanganan error, rekayasa ketahanan, strategi retry, circuit breaker, dan degradasi anggun di React, Next.js, dan Node.js."
author: "vibes-plug-swarm"
---

# Error & Resilience Expert (2026 Edition)

[English](#english) | [Bahasa Indonesia](#bahasa-indonesia)

---

<a name="english"></a>
## English

### Orchestration & Integration
Connects and orchestrates with relevant domain skills like `brainstorming`, `zero-to-prod-orchestrator`, and `project-context-mapper` to ensure cohesive execution.

### Description
Production-grade guide for building resilient applications that fail gracefully, recover automatically, and provide actionable error feedback to users and developers. Covers **React Error Boundaries**, **Next.js error handling** (`error.tsx`, `global-error.tsx`, `not-found.tsx`), **API error response standards** (RFC 9457 Problem Details), **retry patterns** with exponential backoff, **circuit breaker patterns**, **dead letter queues**, and **Sentry/BugSnag** integration.

### Trigger Conditions
Activate this skill when:
- Setting up error handling for React/Next.js applications.
- Implementing retry logic for unreliable API calls or third-party services.
- Designing circuit breaker patterns for microservice-to-microservice calls.
- Building fallback UI for degraded service states.
- Integrating error tracking tools (Sentry, BugSnag, LogRocket).
- Handling transaction failures in database operations.
- Designing dead letter queues for failed async jobs.

---

### Core Concepts

#### Error Handling Philosophy

| Principle | Description |
|---|---|
| **Fail Fast** | Detect and report errors early; don't let invalid state propagate |
| **Fail Gracefully** | Show useful fallback UI, not blank screens or raw stack traces |
| **Retry Intelligently** | Use exponential backoff + jitter; never retry non-idempotent operations blindly |
| **Isolate Failures** | A failing component shouldn't crash the entire page |
| **Track Everything** | Every unhandled error must reach your monitoring system |

---

### 1. React & Next.js Error Handling

#### Error Boundaries (React 19)
```tsx
// components/error-boundary.tsx
'use client';

import { Component, type ErrorInfo, type ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
}

interface State {
  hasError: boolean;
  error: Error | null;
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null };

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    this.props.onError?.(error, errorInfo);
    // Report to Sentry/BugSnag
    if (typeof window !== 'undefined' && window.Sentry) {
      window.Sentry.captureException(error, { extra: errorInfo });
    }
  }

  reset = () => this.setState({ hasError: false, error: null });

  render() {
    if (this.state.hasError && this.state.error) {
      const { fallback } = this.props;
      return typeof fallback === 'function'
        ? fallback(this.state.error, this.reset)
        : fallback;
    }
    return this.props.children;
  }
}
```

#### Next.js App Router Error Files
```tsx
// app/error.tsx — Route-level error handler
'use client';

export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <div role="alert" className="error-container">
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      {error.digest && <p className="text-muted">Error ID: {error.digest}</p>}
      <button onClick={reset}>Try again</button>
    </div>
  );
}

// app/not-found.tsx — 404 handler
export default function NotFound() {
  return (
    <div>
      <h2>Page Not Found</h2>
      <p>The page you are looking for does not exist.</p>
    </div>
  );
}

// app/global-error.tsx — Root layout error handler (catches layout errors)
'use client';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={reset}>Try again</button>
      </body>
    </html>
  );
}
```

---

### 2. API Error Response Standards (RFC 9457)

```typescript
// lib/api-error.ts
export class ApiError extends Error {
  constructor(
    public readonly status: number,
    public readonly code: string,
    message: string,
    public readonly details?: Record<string, unknown>,
  ) {
    super(message);
    this.name = 'ApiError';
  }

  /** RFC 9457 Problem Details JSON response */
  toJSON() {
    return {
      type: `https://api.example.com/errors/${this.code}`,
      title: this.code.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
      status: this.status,
      detail: this.message,
      ...(this.details && { extensions: this.details }),
    };
  }
}

// Usage in API route
export async function POST(request: Request) {
  try {
    const body = await request.json();
    const parsed = createUserSchema.safeParse(body);
    if (!parsed.success) {
      throw new ApiError(422, 'validation_error', 'Invalid request body', {
        errors: parsed.error.flatten().fieldErrors,
      });
    }
    // ...business logic
  } catch (error) {
    if (error instanceof ApiError) {
      return Response.json(error.toJSON(), { status: error.status });
    }
    // Unexpected error — log and return generic 500
    console.error('Unhandled error:', error);
    return Response.json(
      { type: 'about:blank', title: 'Internal Server Error', status: 500 },
      { status: 500 },
    );
  }
}
```

---

### 3. Retry Patterns with Exponential Backoff

```typescript
// lib/retry.ts
interface RetryOptions {
  maxRetries?: number;
  baseDelayMs?: number;
  maxDelayMs?: number;
  /** Only retry if this returns true */
  retryIf?: (error: unknown) => boolean;
  onRetry?: (attempt: number, error: unknown) => void;
}

export async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {},
): Promise<T> {
  const {
    maxRetries = 3,
    baseDelayMs = 500,
    maxDelayMs = 30_000,
    retryIf = isRetryable,
    onRetry,
  } = options;

  let lastError: unknown;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (attempt === maxRetries || !retryIf(error)) {
        throw error;
      }

      // Exponential backoff with jitter
      const delay = Math.min(
        baseDelayMs * 2 ** attempt + Math.random() * baseDelayMs,
        maxDelayMs,
      );

      onRetry?.(attempt + 1, error);
      await sleep(delay);
    }
  }

  throw lastError;
}

function isRetryable(error: unknown): boolean {
  if (error instanceof ApiError) {
    // Retry 429 (rate limit), 502/503/504 (server errors)
    return [429, 502, 503, 504].includes(error.status);
  }
  // Retry network errors
  if (error instanceof TypeError && error.message.includes('fetch')) return true;
  return false;
}

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
```

---

### 4. Circuit Breaker Pattern

```typescript
// lib/circuit-breaker.ts
type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';

interface CircuitBreakerOptions {
  failureThreshold?: number;
  resetTimeoutMs?: number;
  halfOpenMaxAttempts?: number;
}

export class CircuitBreaker {
  private state: CircuitState = 'CLOSED';
  private failureCount = 0;
  private lastFailureTime = 0;
  private halfOpenAttempts = 0;

  constructor(
    private readonly name: string,
    private readonly options: CircuitBreakerOptions = {},
  ) {}

  private get failureThreshold() { return this.options.failureThreshold ?? 5; }
  private get resetTimeoutMs() { return this.options.resetTimeoutMs ?? 60_000; }

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime >= this.resetTimeoutMs) {
        this.state = 'HALF_OPEN';
        this.halfOpenAttempts = 0;
      } else {
        throw new Error(`Circuit breaker "${this.name}" is OPEN`);
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  private onFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }

  getState(): CircuitState { return this.state; }
}

// Usage
const paymentCircuit = new CircuitBreaker('stripe-api', {
  failureThreshold: 3,
  resetTimeoutMs: 30_000,
});

const charge = await paymentCircuit.execute(() =>
  stripe.paymentIntents.create({ amount: 1000, currency: 'usd' })
);
```

---

### 5. Graceful Degradation & Fallback UI

```tsx
// components/resilient-data.tsx
'use client';

import { useSuspenseQuery } from '@tanstack/react-query';
import { Suspense } from 'react';
import { ErrorBoundary } from './error-boundary';

// Pattern: Wrap data-dependent UI with ErrorBoundary + Suspense
export function ResilientDataSection() {
  return (
    <ErrorBoundary
      fallback={(error, reset) => (
        <div className="degraded-state">
          <p>Unable to load latest data. Showing cached version.</p>
          <button onClick={reset}>Retry</button>
        </div>
      )}
    >
      <Suspense fallback={<DataSkeleton />}>
        <LiveDataSection />
      </Suspense>
    </ErrorBoundary>
  );
}
```

---

### 6. Dead Letter Queue for Failed Jobs

```typescript
// queues/dead-letter.ts
import { Queue, Worker } from 'bullmq';

const mainQueue = new Queue('email-send', { connection: redis });
const deadLetterQueue = new Queue('email-send-dlq', { connection: redis });

const worker = new Worker('email-send', async (job) => {
  await sendEmail(job.data);
}, {
  connection: redis,
  settings: {
    backoffStrategy: (attemptsMade) => {
      // Exponential backoff: 1s, 4s, 16s, 64s
      return Math.min(1000 * 4 ** attemptsMade, 120_000);
    },
  },
});

worker.on('failed', async (job, err) => {
  if (job && job.attemptsMade >= (job.opts.attempts ?? 3)) {
    // Move to dead letter queue for manual investigation
    await deadLetterQueue.add('failed-email', {
      originalJobId: job.id,
      data: job.data,
      error: err.message,
      failedAt: new Date().toISOString(),
    });
  }
});
```

---

### Common Pitfalls to Avoid

| Anti-Pattern | Problem | Correct Approach |
|---|---|---|
| Catching errors silently (`catch (e) {}`) | Errors disappear, bugs hide | Always log or re-throw with context |
| Retrying non-idempotent operations | Duplicate charges, double inserts | Only retry reads and idempotent writes |
| No timeout on external calls | Thread/connection pool exhaustion | Set `AbortSignal.timeout()` on all fetch calls |
| Generic "Something went wrong" for all errors | Users can't self-resolve | Show actionable messages (retry, contact support, check input) |
| Using `try/catch` around every single line | Code becomes unreadable | Use error boundaries and middleware for batch handling |

---

### Integration with Other Skills

- `js-backend-expert` — API middleware error handling and response formatting
- `senior-frontend` — React Error Boundaries and Suspense patterns
- `database-orm-expert` — Transaction error handling and rollback patterns
- `logging-error-tracking-expert` — Sentry/BugSnag integration and structured error logging
- `async-queue-temporal-expert` — Dead letter queues and retry strategies for background jobs
- `production-ready-hardener` — Pre-launch error handling audit checklist
- `api-design-expert` — RFC 9457 error response specification

---

<a name="bahasa-indonesia"></a>
## Bahasa Indonesia

### Integrasi Orkestrasi
Terhubung dan mengorkestrasi skill domain yang relevan seperti `brainstorming`, `zero-to-prod-orchestrator`, dan `project-context-mapper` untuk memastikan eksekusi yang kohesif.

### Deskripsi
Panduan tingkat produksi untuk membangun aplikasi yang tahan banting — gagal secara anggun, pulih otomatis, dan memberikan umpan balik error yang dapat ditindaklanjuti kepada pengguna maupun developer. Mencakup **React Error Boundaries**, **penanganan error Next.js** (`error.tsx`, `global-error.tsx`, `not-found.tsx`), **standar respons error API** (RFC 9457 Problem Details), **pola retry** dengan exponential backoff, **pola circuit breaker**, **dead letter queues**, dan integrasi **Sentry/BugSnag**.

### Kondisi Pemicu
Aktifkan skill ini ketika:
- Menyiapkan penanganan error untuk aplikasi React/Next.js.
- Mengimplementasikan logika retry untuk panggilan API yang tidak stabil atau layanan pihak ketiga.
- Merancang pola circuit breaker untuk panggilan antar-microservice.
- Membangun UI fallback untuk kondisi layanan terdegradasi.
- Mengintegrasikan alat pelacak error (Sentry, BugSnag, LogRocket).
- Menangani kegagalan transaksi pada operasi database.
- Merancang dead letter queue untuk job asinkron yang gagal.

### Konsep Inti

#### Filosofi Penanganan Error

| Prinsip | Deskripsi |
|---|---|
| **Gagal Cepat** | Deteksi dan laporkan error sedini mungkin; jangan biarkan state invalid menyebar |
| **Gagal dengan Anggun** | Tampilkan UI fallback yang berguna, bukan layar kosong atau stack trace mentah |
| **Retry dengan Cerdas** | Gunakan exponential backoff + jitter; jangan retry operasi non-idempoten secara membabi buta |
| **Isolasi Kegagalan** | Komponen yang gagal tidak boleh menghancurkan seluruh halaman |
| **Lacak Semuanya** | Setiap error yang tidak tertangani harus sampai ke sistem monitoring |

### Kesalahan Umum yang Harus Dihindari

| Anti-Pola | Masalah | Pendekatan yang Benar |
|---|---|---|
| Menangkap error diam-diam (`catch (e) {}`) | Error menghilang, bug tersembunyi | Selalu log atau lempar ulang dengan konteks |
| Retry operasi non-idempoten | Charge ganda, insert duplikat | Hanya retry operasi baca dan write idempoten |
| Tidak ada timeout pada panggilan eksternal | Thread/pool koneksi habis | Pasang `AbortSignal.timeout()` pada semua panggilan fetch |
| Pesan generik "Terjadi kesalahan" untuk semua error | Pengguna tidak bisa menyelesaikan sendiri | Tampilkan pesan yang actionable (retry, hubungi support, periksa input) |

### Integrasi dengan Skill Lain

- `js-backend-expert` — Middleware penanganan error API dan pemformatan respons
- `senior-frontend` — React Error Boundaries dan pola Suspense
- `database-orm-expert` — Penanganan error transaksi dan pola rollback
- `logging-error-tracking-expert` — Integrasi Sentry/BugSnag dan logging error terstruktur
- `async-queue-temporal-expert` — Dead letter queue dan strategi retry untuk background job
- `production-ready-hardener` — Checklist audit penanganan error sebelum peluncuran
- `api-design-expert` — Spesifikasi respons error RFC 9457
