Support for renderToPipeableStream and renderToStringWithData?

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 renderToString.

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?

Using 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 Switch and 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.