In looking at the docs, my understanding is that server side rendering with Apollo is supported by
renderToStringWithData which will fetch all necessary queries and then return a rendered string. However, it does not work with React.Suspense and React.lazy for code splitting and lazily loaded components. For server side rendering to work with lazy loaded components you need to use
renderToPipeableStream instead of
Because of this, you can’t seem to use Apollo in server side rendering if you are lazily loading components. Is this correct?
Are there solutions for getting server side rendering to work with Apollo when lazy loading?
renderToPipeableStream opts you into React 18’s “streaming SSR” mode, which means that it won’t wait for the full page to render on the server, but sends over incremental chunks.
That also means that Apollo Client needs to send over incremental chunks - but React doesn’t have a mechanism for that kind of data synchronization.
As this point, we have support for streaming SSR with Next.js with a separate package you can find at GitHub - apollographql/apollo-client-nextjs: Apollo Client support for the Next.js App Router, and also an experimental build for Redwood.js that you can find here: do-not-merge: experiment(redwood): Change import of ServerHtmlContext to redwood by dac09 · Pull Request #91 · apollographql/apollo-client-nextjs · GitHub
Both of these rely on a “insert data into the stream” functionality that is build by those frameworks. If you really want to use this in a custom streaming SSR scenario, you’d have to recreate that “insert into stream” functionality and then fork our package to use that. I fear that’s not really feasible, though.
But unfortunately: as long as React doesn’t decide to ship that on it’s own, it’s the only option. We can’t make it work with “just React” at this point.
Thanks Lenz, appreciate the breakdown. One follow up question:
I’ve removed all lazy loading in the app and am using
renderToStringWithData to see if I can get Apollo to work with basic
renderToString. However, calling
renderToStringWithData seems to traverse our app and call just about every single
useQuery we have even if it’s not getting rendered. For SSR, we have a
StaticRouter and the corresponding
Route components that our original client application was using (except the client was using
BrowserRouter in place of
StaticRouter). Any idea why it’s calling all the un-rendered
useQuery references? As you can imagine it’s slowing things down dramatically.
To be honest, that seems pretty much impossible as you describe it. Apollo Client doesn’t look into your React components to discover unrendered trees and render those - it relies on what React is rendering to call
useClient. Maybe your router is doing something unexpected here?
Ok thanks. We’ll have to look into the Router then.