Failing Gracefully: Error Handling Strategy for React SSR Applications
Handling errors is a key part of creating strong React applications, especially when it comes to server-side rendering. A thoughtfully designed error management strategy not only enhances the user experience but also enables developers to swiftly pinpoint and resolve issues. In this post, we'll dive into a (somewhat) detailed approach to error handling for React SSR applications.
Client-Side Error Handling
React Error Boundaries
Create a reusable error boundary component that catches errors in its child component tree:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Log error to monitoring service
console.error("Error caught by boundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback || <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
Hierarchical Error Boundaries
Wrap different sections of your application with error boundaries to prevent entire app crashes:
- Root-level boundary for catastrophic errors
- Page-level boundaries for route-specific errors
- Feature-level boundaries for component isolation
Server-Side Error Handling
Express Middleware for SSR
Implement error handling middleware:
app.get('*', async (req, res, next) => {
try {
// Your SSR logic here
const html = renderToString(<App />);
res.send(html);
} catch (error) {
next(error); // Pass to error handling middleware
}
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error('Server render error:', error);
// Return graceful error page
const errorHtml = renderToString(<ServerError error={error} />);
res.status(500).send(errorHtml);
});
Route-Specific Error Handling
Create custom handlers for different error types:
- 404 handlers for not found resources
- 401/403 handlers for authentication/authorization errors
- Generic 500 error handler for server issues
Data Fetching Error Management
HOC or Custom Hooks
Create a data fetching wrapper with built-in error handling:
function useDataFetching(fetchFn) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
try {
const result = await fetchFn();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
})();
}, [fetchFn]);
return { data, error, loading };
}
Error State Management
Use a state management solution (Redux, Context API) to handle global error states:
- API request failures
- Authentication errors
- Network connectivity issues
Monitoring and Reporting
Error Logging Service
Integrate with services like Sentry, LogRocket, or your own backend:
// During application initialization
import * as Sentry from '@sentry/react';
Sentry.init({
dsn: "YOUR_DSN",
integrations: [
new Sentry.BrowserTracing(),
],
tracesSampleRate: 1.0,
});
Custom Error Reporter
Create a utility to normalize and report errors:
export const reportError = (error, context = {}) => {
// Add environment information
const errorDetails = {
message: error.message,
stack: error.stack,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
...context
};
// Send to your API or logging service
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify(errorDetails),
}).catch(console.error); // Avoid error reporting loops
};
User Feedback and Recovery
Error Display Components
Create a library of error presentation components:
- Toast notifications for non-critical errors
- Modal dialogs for important errors
- Inline error messages for form validation
- Full-page error displays for critical failures
Recovery Actions
Provide users with ways to recover:
- Retry buttons for network requests
- Refresh page options
- Clear cache/data options
- Contact support links
Embracing these patterns means you can build a solid error handling strategy for your React SSR application. This not only keeps things steady but also enhances user experience and makes debugging and maintenance a breeze!
This post explores best practices for managing errors in React server-side rendering applications. The strategies covered here can help improve the stability and user experience of your web applications while making debugging and maintenance easier for development teams.