Open wintercounter opened 2 years ago
Is there any particular reason you’d rather not migrate to the streaming renderToPipeableStream
method? That one would emit all HTML (whether it’s lazy on the server too or not), and is generally recommended in 18 over renderToString
.
Many libs do not support Suspense for data loading yet, like Apollo as well. For example Apollo's SSR currently works by doing complete rerenders while there are unresolved queries, then once it's done, I get all the data from the client, and manually add to the source using window.APOLLO_STATE
. I don't see how this method can work with streams/pipe, maybe by using a custom stream first instead of Express's and then send it once it's ready, but that would be the same as renderToString
at the end, doesn't it? :)
Anyway, it's not up to me to refactor these, I also don't really need streams anyway, I'm using a Redis cache for the entire response by URL as most of the content is static.
I'm using Apollo Client with SSR, streaming is not an option for me, but bundle-splitting was always a pain point. I decided to investigate a bit. After a few hacks/adjustments, I'm having a fully working SSR site of React 18, Lazy components + Bundle splitting, and Apollo Client.
Docs/publications are stating that it's not possible because during render it'll immediately return upon finding a Promise. This is what was called limited Suspense support. However, in my case, I only need Suspense for Lazy.
I started to experiment with React 18 + Apollo's
getDataFromTree
(what I used before with 17 until now). I had to switch fromrenderToStaticMarkup
torenderToString
to preserve the special markers React is using during hydration. This wasn't a problem, Apollo supports passing a customrenderFunction
which allowed me to replace.Testing this simple setup with lazy components led to some interesting results. SSR was working, but on each refresh always new parts started to appear in the markup. After a few refresh the app reached it's final state and it was always correct from that point. I suspected first that Node's cached modules are the reason, tried to require all lazy modules on server side to pre-cache them, it didn't work.
Then I checked the source code of
renderToString
where I saw that lazy is actually caching the resolved values for later use.I simply patched the created lazy modules to make it look like it's initialized, and voila, my application became sync on server side, everything works.
With all this, I was wondering if there's room later to provide an API for this. It's not a big deal IMO, and it'd be incredibly helpful to all people relying on
renderToString
.For example something like
lazy(() => import('./Search'), typeof global !== 'undefined')
, where the second params isevalute
. I'd even make it evaluated by default on server side, because why not?! :)