Intended outcome:
I would like for my React app to render once on either side. Once on the server, get the state -> hydrate on client and render once.
It could end up causing quite a performance hit if I am essentially rendering my app 4 times for every request.
Actual outcome:
I've set up a simple console.log("Rendering App") in my App render method. It shows that while the server is serving the request my app is rendering twice and also on the client my app is rendering twice:
Server:

Client:

It only happens when I use getDataFromTree() on the server side and on the client side it only occurs when I create my ApolloClient using an initial state in the options, in my case like this: initialState: window.__APOLLO_STATE__.
There are no other network requests going out from the client, so no queries being enacted etc. which might cause a re-render.
I managed to find the render methods being called in Chrome Devtools:
You can see the first App.render a bit on the left there and the final (much shorter one) is on the right there in the blue bordered block

Both of those App.render methods happen almost directly after a proxiedMethod and GraphQL.render method.
How to reproduce the issue:
On the server (the method that returns all the stuff I place into my HTML response):
const apolloClient = new ApolloClient({
ssrMode: true,
networkInterface: createNetworkInterface({
uri: "https://api.graph.cool/simple/v1/cj6dds6fd2t2z0101yktqzgsd",
}),
});
console.log("Rendering to React");
const app = wrapAppComponent(App, apolloClient);
await getDataFromTree(app);
const appHtml = renderToString(app);
return { reactHtml: appHtml, reactState: {apollo: apolloClient.getInitialState()} };
On the client:
const apolloState = window.__APOLLO_STATE__;
const networkInterface = createNetworkInterface({
uri: "https://api.graph.cool/simple/v1/cj6dds6fd2t2z0101yktqzgsd",
});
const client = new ApolloClient({
networkInterface,
initialState: apolloState,
ssrForceFetchDelay: 100,
});
Version
@lostpebble hmm that is no good! Do you have a simple reproduction I can take a look at?
@jbaxleyiii sure, I took my current project and tried to strip it down as much as possible to create something you can run and check out:
https://github.com/lostpebble/apollo-two-render
Hope it's not too verbose. The main files to look at are ServerRenderReact.tsx and entryProd.tsx - for the server side rendering. And then entryHot.tsx for the client side rendering.
@jbaxleyiii any update on this?
This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!
I have same issue when using getDataFromTree on server side :(, any Update on this ?
This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!
Any updates on this issue?
This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!
I can't say for the server rendering code, I've yet to use something like this. But for the client side, it seems logical to have two render. Maybe it's different in typescript from pure javascript, but a graphql query would first send { loading: true, allLinks: undefined } and then send { loading: false, allLinks: ...YOUR_DATA }, which would make 2 renders with your code. And if the "loading" is managed differently in typescript, there is also #1314 that can maybe cause this?
This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!
@malexandre with SSR the client will not have loading as true cause the app already has the data before the first render
This is definitely an issue.
in graphql.jsx the data for the children is being loaded on render and for every change in the data the subscription will force a render in the children. The issue comes because the subscription is forcing an update in the first render even without changes to the data so every child is rendered twice.
I tried to silence the first incoming data in the subscription and it worked well in my app but the test recycled-queries broke on the line 236 basically because there may be a chance that a mutation happened while the component is being mount and the subscription should bring the updated data (although I didn't confirm if it was the real reason) but every component was getting rendered only once for the same data.
I hope this can be useful to solve it, thanks
I inspected my Node SSR server, and it turns out renderToString is called a bajillion times for each request. This is one page that's server-side rendered:

(note: this is the flamegraph for Node, not the clientside)
Click to see my SSR code
const renderer = (req, res) => {
// Create an Apollo Client with a local network interface
const client = new ApolloClient({
ssrMode: true,
networkInterface: createNetworkInterface({
uri: IS_PROD ? `https://${req.hostname}/api` : 'http://localhost:3001/api',
opts: {
// Send credentials on
credentials: 'include',
// Forward the cookies to the API so it can authenticate the user
headers: {
cookie: req.headers.cookie,
},
},
}),
...getSharedApolloClientOptions(),
});
// Define the initial redux state
const initialReduxState = {
users: {
currentUser: req.user,
},
};
// Create the Redux store
const store = initStore(initialReduxState, {
// Inject the server-side client's middleware and reducer
middleware: [client.middleware()],
reducers: {
apollo: client.reducer(),
},
});
let modules = [];
const report = moduleName => {
modules.push(moduleName);
};
const context = {};
const frontend = (
<Loadable.Capture report={report}>
<ApolloProvider store={store} client={client}>
<StaticRouter location={req.url} context={context}>
<Routes maintenanceMode={IN_MAINTENANCE_MODE} />
</StaticRouter>
</ApolloProvider>
</Loadable.Capture>
);
// Initialise the styled-components stylesheet and wrap the app with it
const sheet = new ServerStyleSheet();
renderToStringWithData(sheet.collectStyles(frontend))
.then(content => {
if (context.url) {
res.redirect(301, context.url);
return;
}
// Get the resulting data
const state = store.getState();
const helmet = Helmet.renderStatic();
res.status(200);
const bundles = getBundles(stats, modules)
// Create <script defer> tags from bundle objects
.map(bundle =>
createScriptTag({ src: `/${bundle.file.replace(/\.map$/, '')}` })
)
// Make sure only unique bundles are included
.filter((value, index, self) => self.indexOf(value) === index);
const scriptTags = [...bundles].join('\n');
// Compile the HTML and send it down
res.send(
getHTML({
content,
state,
styleTags: sheet.getStyleTags(),
metaTags:
helmet.title.toString() +
helmet.meta.toString() +
helmet.link.toString(),
scriptTags,
})
);
res.end();
})
};
export default renderer;
Any ideas why renderToString is called five times for a single request?
@mxstbr If you are interested in perf issues you can have a look at: https://github.com/apollographql/apollo-client/issues/2359 https://github.com/apollographql/react-apollo/issues/1258 https://github.com/apollographql/apollo-client/issues/2344
renderToString is called a bajillion times
@mxstbr I don't think it's what happening. The flame chart shows that renderToString is interrupted and resumed bajillion times.
In my case getDataFromTree can be very expensive (100ms). I have added a flag to disable it entirely on the pages that don't need it.
Maybe something like this? https://github.com/apollographql/react-apollo/issues/1718
Any updates on this issue?
I have run into the same thing causing various weird unexpected behaviors in my app due to the duel render/constructors. This occurs because getDataFromTree and ReactDOM.renderToString both construct independent instances of the application.
This can lead to really baffling behaviors if you try to build your ApolloClient instance inside of a React component instead of outside of the React app in your SSR code. Specifically it causes the SSR to fail because it builds up all of the data requests in getDataFromTree on the first render/constructor, but then it throws that app away and builds a new one in ReactDOM.renderToString with a fresh ApolloClient built in the constructors of the components again but now with none of the data from the initial tree traversal. Obviously this can be avoided by pulling the ApolloClient construction out of the React app, but why should you have to do that.
Not to mention just the performance cost of rendering/constructing the entire app twice which is generally not needed. Obviously some stuff needs to update once you have the data from your GraphQL service.
I've also run into this issue which was extremely hard to find. Right now I have to resort to removing defaults from the cache, which obviously not ideal.
I have to do something like the following:
static async getInitialProps ({ apolloClient }) {
try {
apolloClient.readQuery({ query: QUERY_GET_FACET })
} catch (e) {
apolloClient.writeData({ data: DEFAULT })
}
apolloClient.mutate({
mutation: MUTATION_WRITE_FACET
})
return {}
}
Anyone else have a better workaround?
I think the best approach is as mentioned, moving the client initialization out of the react app. IMO it doesn't really belong there anyways, and using a hoc to initialize the client and then wrap the app in the necessary provider makes more sense.
@richbai90 I think you sort of missed the point. The usage you describe is fine for most people, but there are legitimate use cases where moving the client initialization out of the React app is problematic. For example, what if the GraphQL service you want to hit is dynamic based on the URL path? Or based on some user interaction in the app? Obviously you could write a bunch of duplicate route definitions on the server side to handle the URL case, but that feels very wrong when you already have React Router doing that kind of work in the React App.
But the above point aside, there are still 2 other main issues why this is problematic for everyone:
And both of these issues can cascade. What if you have a component that does some expensive operations in its constructor or other methods that run before first render? You are going to be doing all of that twice on the server. Or what if you have analytics code or something similar logging events/renders of your components to a service, all of those could get logged duplicate times due to the 2 separate apps.
The only option here is don't use any of Query component on the server, so getDataFromTree will be fast.
Use getInitialProps to request data instead.
I don't use getDataFromTree at all.
@revskill10 Thanks for the suggestion. Unfortunately, getInitialProps is a Next.js thing, not React, so not everyone has that option. Also, that is sort of beside to point of fixing the behavior of the functionality that all of the docs for react-apollo say to use to do SSR - getDataFromTree.
https://reactjs.org/blog/2018/11/27/react-16-roadmap.html#suspense-for-server-rendering
The React team is moving in direction to solve this problem.
We started designing a new server renderer that supports Suspense (including waiting for asynchronous data on the server without double rendering) and progressively loading and hydrating page content in chunks for best user experience.
If u already have the pre-rendered markup in the index.html, use the following code to avoid reloading
const rootEle = document.getElementById('root');
if (rootEle.hasChildNodes) {
ReactDOM.hydrate(<App />, rootEle);
} else {
ReactDOM.render(<App />, rootEle);
}
Any update on this?
React Apollo has been refactored to use React Hooks behind the scenes for everything, which means a lot has changed since this issue was opened (and a lot of outstanding issues have been resolved). We'll close this issue off, but please let us know if you're still encountering this problem using React Apollo >= 3.1.0. Thanks!
Just FYI for anyone who comes across this like I did...
My problem ended up being something entirely not react-apollo relevant. I use styled-components and on SSR this means using the styled-components library to insert your styles into the rendered HTML:
const sheet = new ServerStyleSheet();
const styledApp = sheet.collectStyles(App);
const content = ReactDOMServer.renderToString(styledApp);
const state = client.extract();
const styles = sheet.getStyleElement();
const html = (
<Html
content={content}
state={state}
styles={styles}
/>
);
After a lot of debugging I realized that the suspected "double render" was actually a double request to my server for any full page load, and that if I didnt include styles in the rendered HTML the double request wouldn't happen...
Lo and behold, I had the following somewhere in my styles:
background-image: url();
Hence, the empty url value was causing the page to be fetched twice. Im an idiot, maybe this will help someone.
Most helpful comment
Just FYI for anyone who comes across this like I did...
My problem ended up being something entirely not
react-apollorelevant. I usestyled-componentsand on SSR this means using thestyled-componentslibrary to insert your styles into the rendered HTML:After a lot of debugging I realized that the suspected "double render" was actually a double request to my server for any full page load, and that if I didnt include styles in the rendered HTML the double request wouldn't happen...
Lo and behold, I had the following somewhere in my styles:
Hence, the empty
urlvalue was causing the page to be fetched twice. Im an idiot, maybe this will help someone.