Designing Error Boundaries with Claude Code: From Frontend to API
Designing error boundaries using Claude Code. From frontend to API. Includes practical code examples.
What Is an Error Boundary?
An error boundary is a defensive design pattern that prevents the entire application from crashing when part of it throws an error. With Claude Code, you can design consistent error handling across both the frontend and the backend.
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>Something went wrong</h2>
<p>{this.state.error?.message}</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try again
</button>
</div>
)
);
}
return this.props.children;
}
}
Hook for Function Components
import { useCallback, useState } from "react";
function useErrorHandler() {
const [error, setError] = useState<Error | null>(null);
if (error) {
throw error; // Caught by the parent 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 };
}
Standardizing API Error Responses
// Error code definitions
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);
}
}
Global Error Handler
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) {
// Business error
return res.status(err.statusCode).json({
code: err.code,
message: err.message,
details: err.details,
requestId,
timestamp: new Date().toISOString(),
});
}
// Unexpected error
console.error(`[${requestId}] Unexpected error:`, err);
res.status(500).json({
code: ErrorCode.INTERNAL_ERROR,
message: "An unexpected error occurred",
requestId,
timestamp: new Date().toISOString(),
});
}
// Registration order matters: place after your routers
app.use(globalErrorHandler);
Catching Async Errors
// asyncHandler wrapper
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 });
})
);
Hierarchical Error Boundaries
On the React side, you place multiple error boundaries hierarchically. Use this as a reference when asking Claude Code to design error handling. For writing prompts, see 5 tips for better prompts, and for improving code quality, see automating refactoring.
function App() {
return (
<ErrorBoundary fallback={<FullPageError />}>
<Header />
<ErrorBoundary fallback={<SidebarFallback />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<ContentFallback />}>
<MainContent />
</ErrorBoundary>
</ErrorBoundary>
);
}
For error handling design principles, see MDN Web Docs: Error handling. For Claude Code details, see the official documentation.
Summary
Error boundaries are an important design pattern for preserving user experience. With Claude Code, you can efficiently implement consistent error handling from the frontend all the way through the API.
Free PDF: Claude Code Cheatsheet in 5 Minutes
Just enter your email and we'll send you the single-page A4 cheatsheet right away.
We handle your data with care and never send spam.
Level up your Claude Code workflow
50 battle-tested prompt templates you can copy-paste into Claude Code right now.
About the Author
Masa
Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.
Related Posts
Safe Agent Harness Design for Claude Code and Codex: Permissions, Checks, and Rollback
Build a practical agent harness for Claude Code and Codex with policy, planning, verification, and recovery layers.
10 Powerful Subagent Patterns for Claude Code
Master Claude Code's subagent feature with 10 practical patterns. Learn how to use parallel processing, specialization, and context isolation to double your development speed.
Getting Started with Claude Code Agent SDK — Build Autonomous Agents Fast
Learn how to build autonomous AI agents with Claude Code Agent SDK. Covers setup, tool definitions, and multi-step execution with practical code examples.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.