Advanced

Designing Error Boundaries with Claude Code: From Frontend to API

Designing Error Boundaries: Claude Code 활용. From Frontend to API. 실용적인 코드 예시를 포함합니다.

에러バウンダリとは

에러バウンダリは、애플리케이션の一部で에러が発生しても全体がクラッシュしないようにする防御的な설계パターンです。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의 상세 정보는공식 문서를 확인하세요.

정리

에러バウンダリは사용자体験を損なわないための重要な설계パターンです。Claude Code를 활용하면 프론트엔드からAPIまで一貫した에러処理を효율적으로구현할 수 있습니다。

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