-
Notifications
You must be signed in to change notification settings - Fork 786
Ensure that SSR completes when the GraphQL server throws errors. #488
Conversation
See #406. We still end up rendering a loading screen but that is better than just bailing out of SSR completely.
src/server.ts
Outdated
// we will "forget" this when the "rendering" SSR runs (i.e. we will | ||
// re-run the query, and rendering in a loading state). | ||
// If we change that in future, it may be worth running `getDataFromTree` | ||
// on the subtree, just in case the user runs subqueries in the error state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or is this just a silly thing to worry about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the current behavior is, but I think if the query errors during SSR it's better to have it re-attempt loading on the client rather than showing an error state and not automatically retry. Does that sound reasonable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, and I guess it's what will happen with the current behaviour, definitely[1]
The "silly thing" I'm talking about though is if there are sub-queries inside the "error" UI code path. Realistically I think it's probably fine if we don't support that.
[1] I wouldn't expect it would make much sense to pass error states over in hydrated data for instance.
@calebmer (not sure who is best to ask) - do you think we should open an issue against |
@tmeasday what do you mean by re-using errored queries? What I would expect to happen is that SSR renders the React component with an error as it would on the client. Does that not happen? |
It doesn't because of the way that the SSR mechanism works:
As However, because errored queries are not reused (which is sensible for other reasons), this means that during the Does that make sense? I guess we should make it so that in |
Yeah, I’m not sure how comfortable I am with step 1. That should probably be something we revist in the future. What do you mean by re-using errored queries? Do you mean not throwing an Ideally I’d like to see a way for users to catch any errors and then choose how to handle that. They could either render nothing, opting for a full client render. Render a loading component, or render an error screen. Would that work, and/or how close is this PR to allowing users to handle this kind of error? Right now there isn’t really a nuanced way to handle errors in |
Is there an alternate way to do it? Definitely it's a bit janky but I don't think aside users manually specifying queries at the route level there is a better approach.
Yes, this is what I mean. Currently if you run a second version of the same query against
We could definitely make the I'm not sure if that should be the default, but it would be easy to implement. Perhaps as a separate PR? |
The best thing to do is probably implement a custom asynchronous React renderer. This would also have the benefit of us being able to stream HTML as queries resolved which would be sweet 😊. Hopefully React Fiber will make this easier, and perhaps also force us to reconsider as this approach may just break. Although personally I would prefer users to manually specify queries at the route level. We won’t go that direction at this point, however. In order for users to catch errors and handle it however they want in their own apps we wouldn’t have to change anything, correct? Given that a query promise will be rejected if it failed and then that rejection will propagate to the We know that our current SSR approach does have some limitations, this is one of them. So do we keep patching over those limitations adding complexity in the process, do we choose a fundamentally better approach, or do we accept the tradeoffs and use reasonable workarounds? On principle I generally prefer the latter two. I currently perceive the cost of the patch to |
Maybe we could repurpose the one from next.js[1]? I agree this is the ideal approach; if a fair whack more involved than what we are doing, though. [1] At first blush our
I suppose you are right; I guess this makes sense in a context where server errors are critical; is there a legitimate use case where you would want your SSR to continue to work even though the server has thrown an error? I guess I am asking do we want SSR to work if there are non-critical errors thrown by the GraphQL server? I'm OK with saying it's a limitation of our current approach if we think that's rare. If we go down this road we should add a exception handler to the example: http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree Having said that, this change (to instead not throw errors up and instead just render what we can) is pretty minimal; although it does preclude handling errors "critically".
I'm confused by exactly what you are referring to by "the patch"? Are you saying a patch to AC to "reuse" errored queries? I definitely agree that the added complexity is in no way worth it; especially when you put it in terms of this current SSR approach having a limited shelf life. [If you are referring to this PR, then I'm not sure I agree but I suspect you aren't?] If that is correct then it's a question of which limitation we'd prefer to accept; treating all SSR errors as critical w/ no way to continue; or treating none as critical and confusingly rendering loading states on the server. I'm honestly unsure which is better. |
Yes, that is what I meant. Sorry for lacking clarity 😊
I didn’t know Next.js was using a custom renderer! Do you have a link? 😊 If they are then it would be much easier for us to reach the ideal SSR implementation then I thought. If you want to open an issue for that this may be worth discussing.
If we keep things as they are there is nothing stopping users from ignoring the error and rendering loading states. They would just call |
Hmm, OK Next's SSR isn't that interesting: https://github.com/zeit/next.js/blob/master/server/render.js#L31 / https://github.com/zeit/next.js/blob/master/lib/utils.js#L45 -- they don't do any recursion at all, either within components or w/ multiple levels of promises.
I think I can change this PR pretty simply to complete every query, then throw an error afterwards, as you've suggested. I'll take a look tomorrow, if you agree. Basically the code would just be const errors = [];
return Promise.all(queries.map(...).catch(e => errors.push(e)))
.then(results => {
if (errors.length) {
throw errors;
} else {
return results;
}
}); |
Yeah, that’s how I thought Next.js work. They only load asynchronous props from the root component and then use the Yep, that’s what I was looking for. Although for the errors maybe we should do something like: if (errors.length > 0) {
const error = errors.length === 1
? errors[0]
: new Error(`${errors.length} errors were thrown when executing your GraphQL queries.`);
error.queryErrors = errors;
throw error;
} Which should provide the most information and the best debugging experience 😊 |
@calebmer apologies this dropped off my radar due to technical and life reasons :). Should be good now. |
Looks good to me 😊 Made some small changes, so I’ll merge once CI passes 👍 |
Released in 1.0.0-rc.3 🎉 |
See #406.
We still end up rendering a loading screen but that is better than
just bailing out of SSR completely.
TODO: