Handling Root Errors in SSR Streaming Rendering #122
sebmarkbage
announced in
Deep Dive
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Handling Root Errors in SSR Streaming Rendering
The streaming SSR model is split into two stages. First we render the "shell". The "shell" is all the content outside of Suspense boundaries. This represents the first paint you want to see after the browser switches from displaying the previous page.
Then we fill in those boundaries by streaming the content of those.
If any error happens within any of the Suspense boundaries, those will automatically switch to client-rendering instead of SSR. I.e. the default error handling is to show a loading state (fallback) while we retry on the client before showing an error state.
However, if something errors in the "shell", it's up to you (usually a meta-framework) to render a different response instead.
This means that you usually shouldn't start streaming until the "shell" completes so that you can send an alternative response.
Today, it's common to display a general purpose error page in this scenario. However, we actually recommend a different strategy. We recommend that you set the status code to 500, but instead return a client-rendered page just like if it wasn't server rendered at all. The initial HTML can be blank or a simple loading state.
This approach is more consistent to what we do inside Suspense boundaries.
The idea is that for SEO purposes, proxies and bots, the actual content of the error message doesn't matter. The 500 code just says to not update the cache or index this page. However, for the client viewing this page, it is better to show the actual result but later if it's possible to recover. For example if there was a temporary issue on the server or if there was a bug due to code that only works on the client.
If you know for sure that it is an error that can't be recovered on the client, you can show an error in the initial HTML.
We give you the option to do either regardless.
Web Streams
As of facebook/react#23247, we're changing the Web Streams API to return a Promise of a ReadableStream. This Promise will resolve when the "shell" has fully rendered. If the shell errors, it'll reject the Promise.
This leads to a pretty straight forward recovery.
Node Streams
We're not Promisifying the Node API yet since it exists to work on older versions of Node (that doesn't support AbortController).
However, as of facebook/react#23247, you can use the onShellReady and onShellError callbacks to achieve a similar effect.
Future Preloading
In the future, we'll add a feature to start preloading external resources from within React. This should start streaming even before the shell is complete. The new plan is to expose that from an out-of-band callback:
Ideally this should be done through HTTP 103 Early Hints together with the Link header.
So the HTTP response end up looking something like this. Where the early hints indicates what to preload.
Unfortunately there's no standard API for this in the Web spec and the infrastructure for this is still poor so it might instead be better to start emitting the response early with an optimistic status code of 200 in some environments.
The issue with this is that you need to know you can start flushing the HTML and HEAD tag early and you have to also exclude the doctype and html that comes from the React stream or your error stream when you patch them in. So this means you can't just pass it straight through.
The original plan was to have this all built-in so that it would be easy to use but that has other consequences of making the API for handling errors in the shell harder. So the plan now is to leave this to "user space". However, the bet here is that in the future this will just be handled by Early Hints HTTP headers which should make that easier.
Beta Was this translation helpful? Give feedback.
All reactions