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.