Advanced

Designing Error Boundaries with Claude Code: From Frontend to API

Designing Error Boundaries using Claude Code. From Frontend to API. Includes practical code examples.

エラーバウンダリとは

エラーバウンダリは、アプリケーションの一部でエラーが発生しても全体がクラッシュしないようにする防御的な設計パターンです。Claude Codeを使えば、フロントエンドとバックエンドの両方で一貫したエラー処理を設計できます。

React Error Boundary

import { Component, ErrorInfo, ReactNode } from "react";

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

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

class ErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { hasError: false, error: null };
  }

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

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("Error caught by boundary:", error, errorInfo);
    this.props.onError?.(error, errorInfo);

    // Send to error monitoring service
    reportError(error, {
      componentStack: errorInfo.componentStack,
    });
  }

  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div className="error-fallback">
            <h2>エラーが発生しました</h2>
            <p>{this.state.error?.message}</p>
            <button onClick={() => this.setState({ hasError: false, error: null })}>
              再試行
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}

関数コンポーネント向けフック

import { useCallback, useState } from "react";

function useErrorHandler() {
  const [error, setError] = useState<Error | null>(null);

  if (error) {
    throw error; // 親のError Boundaryでキャッチ
  }

  const handleError = useCallback((err: unknown) => {
    if (err instanceof Error) {
      setError(err);
    } else {
      setError(new Error(String(err)));
    }
  }, []);

  const withErrorHandling = useCallback(
    <T,>(fn: () => Promise<T>) => {
      return async () => {
        try {
          return await fn();
        } catch (err) {
          handleError(err);
          return undefined;
        }
      };
    },
    [handleError]
  );

  return { handleError, withErrorHandling };
}

API エラーレスポンスの標準化

// エラーコード定義
enum ErrorCode {
  VALIDATION_ERROR = "VALIDATION_ERROR",
  NOT_FOUND = "NOT_FOUND",
  UNAUTHORIZED = "UNAUTHORIZED",
  FORBIDDEN = "FORBIDDEN",
  CONFLICT = "CONFLICT",
  INTERNAL_ERROR = "INTERNAL_ERROR",
  RATE_LIMITED = "RATE_LIMITED",
}

interface ApiError {
  code: ErrorCode;
  message: string;
  details?: Record<string, unknown>;
  requestId: string;
  timestamp: string;
}

class AppError extends Error {
  constructor(
    public code: ErrorCode,
    message: string,
    public statusCode: number,
    public details?: Record<string, unknown>
  ) {
    super(message);
    this.name = "AppError";
  }

  static notFound(resource: string) {
    return new AppError(
      ErrorCode.NOT_FOUND,
      `${resource} not found`,
      404
    );
  }

  static validation(details: Record<string, string>) {
    return new AppError(
      ErrorCode.VALIDATION_ERROR,
      "Validation failed",
      400,
      details
    );
  }

  static unauthorized(message = "Authentication required") {
    return new AppError(ErrorCode.UNAUTHORIZED, message, 401);
  }
}

グローバルエラーハンドラー

import { v4 as uuidv4 } from "uuid";

function globalErrorHandler(
  err: Error,
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) {
  const requestId = uuidv4();

  if (err instanceof AppError) {
    // 業務エラー
    return res.status(err.statusCode).json({
      code: err.code,
      message: err.message,
      details: err.details,
      requestId,
      timestamp: new Date().toISOString(),
    });
  }

  // 予期しないエラー
  console.error(`[${requestId}] Unexpected error:`, err);

  res.status(500).json({
    code: ErrorCode.INTERNAL_ERROR,
    message: "An unexpected error occurred",
    requestId,
    timestamp: new Date().toISOString(),
  });
}

// 登録順序が重要:ルーターの後に配置
app.use(globalErrorHandler);

非同期エラーのキャッチ

// asyncHandler ラッパー
function asyncHandler(
  fn: (
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => Promise<void>
) {
  return (
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

// Usage example
router.get(
  "/users/:id",
  asyncHandler(async (req, res) => {
    const user = await prisma.user.findUnique({
      where: { id: req.params.id },
    });

    if (!user) {
      throw AppError.notFound("User");
    }

    res.json({ data: user });
  })
);

階層的エラーバウンダリ

React側では複数のError Boundaryを階層的に配置します。エラー処理の設計をClaude Codeに依頼する際の参考にしてください。プロンプトの書き方は効果的なプロンプト5つのTips、コード品質の改善にはリファクタリング自動化も役立ちます。

function App() {
  return (
    <ErrorBoundary fallback={<FullPageError />}>
      <Header />
      <ErrorBoundary fallback={<SidebarFallback />}>
        <Sidebar />
      </ErrorBoundary>
      <ErrorBoundary fallback={<ContentFallback />}>
        <MainContent />
      </ErrorBoundary>
    </ErrorBoundary>
  );
}

エラーハンドリングの設計原則についてはMDN Web Docs: Error handlingが参考になります。Claude Codeの詳細は公式ドキュメントを確認してください。

Summary

エラーバウンダリはユーザー体験を損なわないための重要な設計パターンです。Claude Codeを使えば、フロントエンドからAPIまで一貫したエラー処理を効率的に実装できます。

#Claude Code #error handling #React #API design #TypeScript