-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 18 SSR Support #8365
Comments
<html>
<body>
...
<script dangerouslySetInnerHTML={{ __html: `
window.INITIAL_APOLLO_STATE = ${myInMemoryCacheInstance.extract()}
` }} />
</body>
</html> This is great! I was thinking about a similar solution, where and how that |
Unfortunately, it's not so easy. The problem is that you'll need Suspense + React.lazy to make this work. If React won't provide any hook level API for suspense then we will still need our own wrappers. Also, this cannot work at all using streaming. The new APIs will provide |
Ah okay that is unfortunate. I guess in my head I assumed we could "throw" a suspender and the server-side suspense would wait for it to resolve, we'd add a new |
Last week I decided to give another try to the topic, so I dug deep into the topic. I've made work Apollo SSR with React 18 and Suspense for lazy loading together, and actually, it's not hard at all. I decided to share my method.
const getDataFromTree = (tree, context) => {
return getMarkupFromTree({
tree,
context,
renderFunction: require('react-dom/server').renderToString
})
}
There is one gotcha, however.On the first render, no data/tree will be returned. Once a lazy module was loaded, it'll start working fine. So this means after page refresh it'll work correctly. To overcome this issue I simply created a separate file on server-side that will load all the available lazy modules upon initialization. In case you have I don't know if this is a limitation of React's SSR, Apollo's Seems like returning an immediately resolved promise for the module keeps rendering "sync" somehow. Anyway, I have a working bundle splitting + Apollo + SSR combo finally! UPDATE: I was excited so I started to migrate a larger codebase. Turns out the above is just half of this module caching story. Seems like I need to have as many reloads as many lazy components I use in the codebase. I'll get the correct markup and preloaded state only after that. It's pretty interesting, I wonder what is happening exactly here. UPDATE 2: Turns out React is caching the resolved lazy values for later use. See here: https://github.com/facebook/react/blob/a724a3b578dce77d427bef313102a4d0e978d9b4/packages/react-dom/src/server/ReactPartialRenderer.js#L1296 I checked the values, and it's simply can be mocked. I'm going to try it tomorrow and come back with the results. UPDATE 3: I created a import * as cards from '@/cards'
import * as layouts from '@/layouts'
import * as screens from '@/screens'
import * as sections from '@/sections'
const modules = [
...Object.entries(cards),
...Object.entries(layouts),
...Object.entries(screens),
...Object.entries(sections)
]
modules.forEach(m => {
const [name, mod] = m
if (!mod._importPath) {
console.log('Error, no _importPath for module', name)
return
}
mod._payload._status = 1
mod._payload._result = require(mod._importPath)
}) This is what the lazy exports look like. I just wanted to keep the import names at the same place, but it's up to you how you patch your module later.
UPDATE 4: I create my own import React, { Suspense, lazy as _lazy } from 'react'
const lazy = (componentFn, importPath) => {
const LazyMod = _lazy(componentFn)
LazyMod._importPath = importPath
const mod = props => (
<Suspense fallback={null}>
<LazyMod {...props} />
</Suspense>
)
mod._lazyMod = LazyMod
return mod
}
export default lazy |
@wintercounter Could you maybe share a small codesandbox for your working bundle splitting + Apollo + SSR combo? That would be great. |
If you provide me a basic Apollo SSR setup, I'll adjust it, but I don't really want to set up the base for this :) I really hate Codesandbox, it's buggy, slow, and annoying to work with :D |
Meanwhile, I have quickly put together a demo: https://github.com/wintercounter/react18-apollo-ssr-bundle-split-demo Since I posted this solution here, I started to use it in production on a large codebase, I don't have any issues. |
Apollo will support threaded rendering? |
Hey all 👋 Just wanted you to be aware that we’ve just opened up an RFC (#10231) where we have a detailed approach to React suspense and SSR. We plan to continue the conversation over there so please have a look! Thanks for all the discussion so far! |
I'd like to start a discussion about React 18 and its changes to Suspense/SSR and what that means to us Apollo users. It was always a pain point for me that I couldn't do proper bundle splitting (w/o 3rd party tools) due to the lack of Suspense support during SSR.
With React 18 we get SSR Suspense support, and it seems like the new
pipeToNodeWritable
API can completely eliminategetDataFromTree
(at least in its current form).The new API will be something like this. (https://codesandbox.io/s/github/facebook/react/tree/master/fixtures/ssr2?file=/server/render.js)
This has an interesting problem: where and how we will pass
INITIAL_APOLLO_STATE
on. The previous common practice was to simply get the data from the cache, put it onwindow
in the markup, load it on the client-side during hydration.For me, it looks like that we will be able to do this without any extra code/workaround now, and we can simply include the above process in our code. Something like:
I haven't experimented with it yet, but I'll soon and get back with the results.
The text was updated successfully, but these errors were encountered: