different server-side rendering behaviour between v2 and v3 #3338
Comments
I'm having a similar issue here
EDIT: |
I can confirm. |
Also seeing this issue. Updating Next did not fix this for me. |
We are seeing the same issue of the data on SSR still showing |
Also experiencing this. The SSR is only returning our loading components. |
I've been debugging this a little bit today and it appears to be isolated to |
@hwillson I was able to "resolve" my issue where a fetchPolicy of
This adjusts the options to be consistent with what I'd also like to point out that I am not sure this addresses the original posters complaint of an additional "loading" render during SSR, but how it works now makes it more consistent with how it works on the client. The main issue I can see is that |
Hi all - question regarding the OP; would people prefer to see the initial Thanks for looking into this @mikebm! I don't think your change will resolve the initially reported issue, but your suggested change makes a lot of sense regardless. Any chance you'd be interested in submitting that as a PR? |
I personally think the loading state should stay. It makes things consistent. In my PR, which I just opened, also addresses an issue where disabling SSR on a query was still resulting in the query being fired but the result ignored. In this case, I had to still return a state when the hook was executed, so I chose to use the ssrLoading event. |
Is there an example of a use case where Currently in our app, because Question: Correct me if I'm wrong, but is this how server-side rendering work in Apollo?:
|
Hello there. 👋 We have been using Apollo for over 2 years at N26 and have been delighted with it so far. We were stoked to update to v3 and migrate to hooks for more elegant components. However… this breaking change is quite a bummer in my opinion. :( I might be missing something, but I genuinely don’t understand the benefit of shipping a loading state to the client. If the server renders with The point of SSR — at least the way we use SSR — is that the page should be readable/usable when hitting the client, so much that we could skip shipping the bundles entirely and have everything there. This change makes that impossible as displaying loaders is not quite achieving that. It would be lovely if we could restore the v2 behaviour somehow. 😊 ✨ |
I completely agree with @hugogiraudel and @cusxio. I think the previous default makes more sense from an SSR "ready for client" perspective. We rely on data being ready and not loading, like I'm not sure what the use case is for ❤️ |
I think the real question is why are your components stuck in a loading state? Ideally your component would never render to the client the loading component during a SSR, except maybe when SSR is off for the query. I feel like you all might be running into a different bug that prevented the query from finishing and either giving an error or data. What fetchPolicy are you guy's using? |
@hwillson It seems like Cookies are not being sent when on SSR unless you manually include |
That's normal. You have to do it manually. On each request, when you build your Apollo Client on the server, you need to pass headers from your request object (req.headers, in the case of express) to createHttpLink. The apollo client doesn't know about express's request nor should it. |
@mikebm got ya, cheers for the explanation |
The components aren’t stuck in loading state, they just begin in loading state then switch to done after it gets the data from cache. But we would like SSR to skip the loading state completely as we already have all the data loaded. It happens with the default fetch policy. To confirm we aren’t having the cookies issue as we already manually attach this to the request. |
So, you are saying the SSR is only returning the loading state to your component and never loading: false, with data populated? In this scenario, you are also saying that your apollo cache was populated with the data. If this is true, then it sounds more like a bug with your component not getting re-rendered with data. I assume you are doing the SSR via something like so?
I was definitely not experiencing this with the default cache policy, which is |
We are using getDataFromTree and passing a Apollo client with initial cached data to it. It’s not that it’s not switching to loading: false, because it is, but they way we wrote our code and what we expect from SSR, is that the components that already have their data from SSR should start off as loading: false. Otherwise we get a flash of loading on all components and stuff like currentUser that we know we already have in cache we would now have to wrap in waiting to finish loading functions. I don’t have time right now but maybe I can try to create a small repo of how we are doing it and make sure it’s not something we are doing wrong. It does seem like others are describing our exact issue. |
I'd really like to get this issue resolved before publishing 3.1.0, so let's all hash out a plan of attack together. BackgroundIn React Apollo 2, the const SomeComponent = () => {
return (
<Query query={CAR_QUERY}>
{({ loading, data }) => {
if (!loading) {
const { make, model, vin } = data.cars[0];
return (
<div>
{make}, {model}, {vin}
</div>
);
}
}}
</Query>
)
}; is rendered on the server, its first pass essentially renders a component that looks like: const SomeComponent = () => null; One advantage render prop functions have over hooks in this situation is that they encapsulate all query result handling, and can then decide if they should or should not return {({ loading, data }) => { This is because internally before running the render prop function with the results of the initial SSR query call (which we haven't received yet from Apollo Client), we just return When it comes to the React Apollo 3 hooks approach however, const SomeComponent = () => {
const { loading, data } = useQuery(CAR_QUERY);
if (!loading) {
const { make, model, vin } = data.cars[0];
return (
<div>
{make}, {model}, {vin}
</div>
);
}
return null;
}; Consumers of SolutionIt is possible to add extra logic to block Looking ahead for ways to change this, would people prefer that we drop the initial SSR const SomeComponent = () => {
const result = useQuery(CAR_QUERY);
if (result) {
const { loading, data } = result;
if (!loading) {
const { make, model, vin } = data.cars[0];
return (
<div>
{make}, {model}, {vin}
</div>
);
}
}
return null;
}; If people don't care about SSR, they don't have to worry about checking for |
I'd still like to narrow down the "it can lead to un-necessary loading: true states being shipped to the client." as this implies the SSR didn't wait for the query to resolve and return data or an error which would have caused the |
@mikebm When |
I understand this, but the server shouldn't be returning those initial results to the client because the SSR will wait for all the renderPromises to resolve, which once they do, it will re-trigger the components to re-render in a non-loading state. So, my question is, aside from the bug I recently fixed, how is it that these components are being returned to the client in a loading state when using the proper SSR features in this library such as |
@mikebm For sure, and great question. The original reproduction links in this issue thread show the So ... I guess we need more details on what the actual issue is there then. Is anyone here able to put together a reproduction that shows how the additional loading state is causing grief? Please note that if you want to use the next.js |
I opened #3390 that shows the error we get in getDataFromTree because of this issue. I now updated the repo further. Here is the github repo And link to code sandbox You can now see that on the initial render we get the loading component. I found that it happens when we query for client data with @client directive, and then in a nested component we query for remote data. |
this might be related: #3486 also @flipxfx you can use apollo with |
Yes but we are fine with opting out on every page, as it’s an internal site that requires Apollo on every page. But for some reason I can’t get it working correctly via the |
ping @hwillson please take a look at #3338 (comment) seems like there are some issues related / around it. |
Btw this is also happening to me when using the |
I agree with the sentiment that when I use SSR I am expecting the page to come with all queries resolved already. I'm sure a ton of us use SSR for SEO purposes and it doesn't do us much good in that regard to show an empty page while we fetch data. The advantage to SSR is handling this on the server and delivering a fleshed out page, otherwise might as well just run with create-react-app and not worry about SSR. |
If renderToStringWithData worked like an iterator and yield each render one by one we could do pre-rendering. Contract would be changed alot but we could send responses as fast as possible to the client and the people who doesn't want to do pre-rendering would just send the last rendered markup. It also would make sense that loading was true at some point. #3545 |
I'm facing the same issue when updating from 2.5.8 to 3.1.3. So if I need the initial server render to resolve all queries for SEO purposes, there's no workaround, the only solution currently is sticking to v2? |
I use it with Next JS and the temporary fix is to call the queries and pass them to the component with getInitialProps. This is how I get the data for SSR |
I was able to resolve the initial SSR issue, it now comes back as loaded, but we are still having an issue when changing routes and the "SSR" not working before changing routes. Example repo: https://github.com/flippidippi/next-apollo-app |
I think there's more going on here, or two different bugs. Those With apollo client 3 getDataFromTree promise seems to resolve right away without waiting for queries to resolve, so the markup is sent with components in loading state. I don't see how returning null instead of {loading: true} fix that or what it would add other than more boilerplate in components. |
In my setup the backend seems to load everything just fine, meaning that the apollo cache state is always filled with the correct cache state. However, in reality, the serverside rendered result is showing the loading state. The client-side always renders the non-loading state because the cache is prefilled from the DOM. I logged the loading states (on the server) and it is: I would love to share a reproducible repo but did not find a proper way to reliably reproduce this issue. I also debugged deep into apollo and could not see why loading is returned, because the cache was also valid for the SSR query handling (checked via the debugger). |
@EyMaddis what is your |
Coming from #3390 I think the behavior of During SSR using Next and
My workaround is reading the cache directly using |
@lifeiscontent I tried multiple strategies, but they do not provide any change. I also made sure (using cache-only) that the default settings are applied (because the app does not load anything then). |
@EyMaddis Can you provide a minimal reproducible use case? |
@lifeiscontent I would love to, unfortunately this only happens seemingly at random while developing. My current guess is that the auto-reload while saving files (in a Next.js project) might break this after a lot of edits. |
Hey, I've just stumbled across this bug and it seems like reusing local state variables inside queries using |
This issue thread has gone in a few different directions which makes it difficult to extract (and track) actionable items. I'll reply to some of the oustanding comments below (the ones that I think encompass the bulk of the questions), but to anyone still having an issue with this, please re-read my comments in #3338 (comment) to understand why React Apollo v2 and v3 handle SSR differently. @seantimm Regarding #3338 (comment), I'd love to hear more. Would you be able to open this in a new issue, possibly with a reproduction? @kedarguy Regarding #3338 (comment), first off thanks for the reproduction. What this reproduction shows however is exactly what I've explained in #3338 (comment). There is an additional loading state happening on the server side, that needs to be handled in your code. You can block the extra loading state from being rendered (which is ultimately leading to the destructuring error you're seeing. There are different ways to address this, but one quick way is to adjust your WithApollo.getInitialProps = async ctx => {
...
return {
...pageProps,
apolloState,
ssrComplete: true
}
} then further adjust your const WithApollo = ({ apolloClient, apolloState, ssrComplete, ...pageProps }) => {
const client = useMemo(
() => apolloClient || initApolloClient(apolloState),
[]
);
if (!ssrComplete) {
return null;
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
} As mentioned in #3338 (comment) I'm all ears if people would like a different solution implemented, but as things stand that initial loading state is required. @bdew Regarding #3338 (comment), the loading @krzksz Regarding #3338 (comment), I updated your repro to use version 3.1.3 of Given that this issue thread has grown a bit unwiedly, I'll close it off. Please open new issues for further SSR related problems. Thanks! |
@hwillson I was facing problem with this issue as well. But I managed to get the final server side rendered state to show the results by just using the rendered string from import { getMarkupFromTree } from '@apollo/client/react/ssr';
import { renderToString } from 'react-dom/server';
const html = await getMarkupFromTree({
tree: App,
context: {},
renderFunction: renderToString
}); instead of await getDataFromTree(App);
const html = renderToString(App); |
Current Behaviour
v2
andv3
ofreact-apollo
have vastly different behaviour/timings server side.On
v3
, it seems thatreact-apollo
has an additional render pass/intermediate state on the server side that breaksv2
behaviour.The additional render pass seems to output the following:
react-apollo/packages/hooks/src/data/QueryData.ts
Lines 172 to 177 in baf9a76
Reproduction:
v2
: https://codesandbox.io/s/with-apollo-xrj2cv3
: https://codesandbox.io/s/with-apollo-8luewCheck out the Terminal output difference of both
v2
andv3
,v2
only has two render pass, butv3
has three render pass.The additional render pass in
v3
currently breaks our app, as we rely on thevariable
data being there.Expected Behaviour
v3
server side rendering behaviour should be similar tov2
The text was updated successfully, but these errors were encountered: