Server-Side Rendering Error Handling

Server-side rendering errors occur before or during HTML generation on the server, so client-side React error boundaries cannot provide the same protection as they do for errors during browser rendering. SSR systems therefore need server-level error handling, appropriate HTTP responses, and a safe fallback strategy.

A React error boundary is primarily a client-rendering mechanism: it catches errors thrown while rendering, in lifecycle methods, and in constructors of descendant components after React is running in the browser. An error that occurs while the server is generating the initial HTML happens before the browser can hydrate that component tree, so a client error boundary cannot catch it.

For SSR, the server should treat rendering as an operation that can fail. With synchronous APIs such as `renderToString`, application code can catch a rendering exception and return an appropriate error response. With streaming APIs, failure can occur after the response has started, so the server must also use the renderer's error callbacks and track whether headers or body data have already been sent.

The HTTP response should reflect the failure appropriately. If rendering fails before a response is committed, the server can usually send a status such as `500` and render a fallback error page. If a stream has already committed part of the response, changing the status code may no longer be possible; the server instead needs a strategy for terminating or recovering the stream without exposing sensitive details.

SSR error handling should also distinguish expected application failures from unexpected server faults. User-facing output should be safe and useful, while detailed exception information should generally be recorded in server-side logs or an error-monitoring system rather than embedded in the HTML response.

After an SSR failure, client-side error boundaries can still become relevant during subsequent browser rendering. They complement server-side handling rather than replacing it.

Key points

Interview questions

Why can't a React Error Boundary catch an SSR error?

An error boundary runs as part of the React tree that is executing in a particular environment. During SSR, the tree is being rendered on the server, before the browser has received and hydrated the page. A browser-side error boundary therefore cannot catch an exception that prevented the server from producing the HTML. The server needs its own error-handling mechanism.

How would you handle an exception from `renderToString`?

I would put the server rendering call inside a `try/catch`. On success, I would send the generated HTML with the normal success status. On failure, I would log the exception for diagnostics and send a safe fallback page with an appropriate 5xx status if the response has not already been committed. I would avoid exposing the original stack trace to the client.

What makes error handling harder with streaming SSR?

A streaming renderer can send part of the HTML before a later rendering failure occurs. Once the response is committed, the server may not be able to change the HTTP status or headers. The server therefore needs renderer-specific error callbacks and a strategy for terminating or completing the response safely. It should also avoid assuming that every rendering failure happens before any bytes are sent.

What HTTP status should an SSR server return when rendering fails?

If the server cannot generate the requested page because of an unexpected rendering failure and the response has not been committed, a 500-class response is generally appropriate. The exact status depends on the failure: an application-level not-found condition may warrant 404, for example, rather than 500. With streaming, the status may already be committed before the failure is discovered, so changing it may be impossible. The important design point is to determine the response semantics before committing output whenever practical.

How would you design SSR error handling for production?

I would establish a server-level rendering boundary, define safe fallback pages, and handle both synchronous and streaming renderer failures. I would preserve meaningful HTTP semantics when possible and explicitly account for whether the response has already been committed. Errors would be logged with request context and correlated with monitoring data, while the client would receive only information appropriate for the environment. I would also test failures before and after response commitment because those cases have different recovery options.