Error Handling Patterns
Comprehensive patterns for managing errors across client-server boundaries, implementing defensive programming practices, and ensuring proper error propagation in production web applications.
Exception Capture Patterns
Explicit Sentry Integration
Caught exceptions must be explicitly reported to monitoring services:
try {
await riskyOperation();
} catch (error) {
// Without this, Sentry won't see the error
Sentry.captureException(error);
return { error: 'Operation failed' };
}
Error Context Preservation
Include relevant context when capturing exceptions:
try {
await syncOperation();
} catch (error) {
Sentry.captureException(error, {
tags: { operation: 'easybeer-sync' },
extra: { userId, timestamp: Date.now() }
});
}
Client-Server Boundary Handling
Network Error Classification
Distinguish between different types of client-side failures:
try {
const response = await fetch('/api/sync');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
} catch (error) {
if (error instanceof TypeError) {
// Network connectivity issue
setError('Erreur de connexion - vérifiez votre réseau');
} else if (error.message.includes('HTTP 5')) {
// Server error
setError('Erreur serveur - réessayez dans quelques instants');
} else {
// JSON parsing or other client error
setError('Erreur inattendue - contactez le support');
}
}
Timeout Handling
Implement explicit timeouts for long-running operations:
const SYNC_TIMEOUT = 300000; // 5 minutes
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), SYNC_TIMEOUT);
try {
const response = await fetch('/api/sync', {
signal: controller.signal
});
} catch (error) {
if (error.name === 'AbortError') {
setError('Synchronisation interrompue - opération trop longue');
}
} finally {
clearTimeout(timeoutId);
}
Defensive Programming Patterns
Array Safety
Protect against undefined array properties:
const sortedClients = useMemo(() => {
if (!portfolio?.clients || !Array.isArray(portfolio.clients)) {
return [];
}
return [...portfolio.clients].sort(/* sorting logic */);
}, [portfolio?.clients]);
Type Guard Functions
Implement runtime type checking for critical data:
function isValidPortfolio(data: unknown): data is Portfolio {
return (
typeof data === 'object' &&
data !== null &&
'clients' in data &&
Array.isArray(data.clients)
);
}
// Usage in component
if (!isValidPortfolio(portfolio)) {
return <ErrorFallback message="Invalid portfolio data" />;
}
Null Coalescing Chains
Handle multiple levels of potentially undefined data:
const clientCount = portfolio?.clients?.length ?? 0;
const hasActiveClients = (portfolio?.clients ?? []).some(client => client.active);
Server-Side Error Patterns
API Route Error Handling
Consistent error responses with proper monitoring:
export async function POST(request: Request) {
try {
const result = await performOperation();
return Response.json({ success: true, data: result });
} catch (error) {
console.error('Operation failed:', error);
Sentry.captureException(error, {
tags: { route: '/api/operation' }
});
return Response.json(
{
success: false,
error: 'Operation failed',
timestamp: new Date().toISOString()
},
{ status: 500 }
);
}
}
Database Error Recovery
Implement retry logic for transient database failures:
async function retryableDbOperation<T>(
operation: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) {
Sentry.captureException(error, {
tags: { operation: 'database-retry-exhausted' }
});
throw error;
}
// Wait before retry (exponential backoff)
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 1000)
);
}
}
}
Serialization Error Patterns
Next.js Server-Client Boundaries
Handle serialization failures during prop passing:
// Server component
export default async function Page() {
try {
const data = await fetchData();
const serializedData = JSON.parse(JSON.stringify(data)); // Test serialization
return <ClientComponent data={serializedData} />;
} catch (serializationError) {
console.error('Serialization failed:', serializationError);
return <ErrorFallback />;
}
}
Date Object Handling
Prevent Date serialization issues:
// Convert Date objects to ISO strings before serialization
function serializeDates<T>(obj: T): T {
return JSON.parse(JSON.stringify(obj, (key, value) => {
if (value instanceof Date) {
return value.toISOString();
}
return value;
}));
}
Error Boundary Patterns
React Error Boundaries
Comprehensive error boundaries with different fallback strategies:
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
Sentry.captureException(error, {
contexts: { errorInfo }
});
}
render() {
if (this.state.hasError) {
return this.props.fallback || <DefaultErrorFallback />;
}
return this.props.children;
}
}
Granular Error Boundaries
Implement specific error boundaries for different application sections:
// Wrap specific features to prevent cascade failures
<DashboardErrorBoundary>
<SyncOperations />
</DashboardErrorBoundary>
<DataVisualizationErrorBoundary>
<Charts />
</DataVisualizationErrorBoundary>
Monitoring Integration Patterns
Structured Error Logging
Consistent error logging format for monitoring:
function logError(error: Error, context: Record<string, unknown>) {
const errorLog = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
context
};
console.error('Application error:', errorLog);
Sentry.captureException(error, { extra: context });
}
Error Metrics
Track error rates and patterns:
// Custom Sentry metrics
Sentry.metrics.increment('sync.attempts');
Sentry.metrics.increment('sync.failures', 1, {
tags: { error_type: error.constructor.name }
});
See also
- sentry-integration
- production-debugging
- Defensive Programming
- Type Safety Patterns