Closed brad-decker closed 1 year ago
I do not know how it can be solved with Next.js.
In my app I'm using double renderToString
call:
It works quite fast and does not force me to make any hacks with ReactRouter4.
I found a solution, I add two files:
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
import { RelayNetworkLayer, cacheMiddleware } from 'react-relay-network-modern/node8';
import { Network, Environment, RecordSource, Store } from 'relay-runtime';
const source = new RecordSource(); const store = new Store(source);
let storeEnvironment = null;
export const { relaySSR, environment, createEnvironment, } = (isBrowser => { if (isBrowser) { const RelaySSR = require('react-relay-network-modern-ssr/node8/client').default;
return {
createEnvironment: relayData => {
if (storeEnvironment) return storeEnvironment;
const relaySSR = new RelaySSR(relayData);
storeEnvironment = new Environment({
store,
network: new RelayNetworkLayer([
cacheMiddleware({
size: 100,
ttl: 60 * 1000,
}),
relaySSR.getMiddleware({
lookup: false,
}),
]),
});
return storeEnvironment;
},
};
}
const RelaySSR = require('react-relay-network-modern-ssr/node8/server').default; const relaySSR = new RelaySSR();
return { relaySSR, environment: new Environment({ store, network: new RelayNetworkLayer([ relaySSR.getMiddleware(), ]), }), createEnvironment: relayData => new Environment({ store, network: Network.create(() => relayData[0][1]), }), }; })(ExecutionEnvironment.canUseDOM);
- withData
```js
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { QueryRenderer } from 'react-relay';
import { environment, createEnvironment, relaySSR } from './createEnvironment';
export default (Component, { query, variables }) => class WithData extends React.PureComponent {
static displayName = `WithData(${Component.displayName})`;
static getInitialProps = async ctx => {
ReactDOMServer.renderToString(
<QueryRenderer
environment={environment}
query={query}
variables={variables}
render={() => null}
/>
);
return {
...(await Component.getInitialProps?.(ctx) || {}), // use for next.js
relayData: await relaySSR.getCache(),
};
}
render() {
const { relayData, ...componentProps } = this.props;
return (
<QueryRenderer
environment={createEnvironment(relayData)}
query={query}
variables={variables}
render={({ error, props }) => {
if (error)
return <div>{error.message}</div>;
else if (props)
return (
<Component
{...componentProps}
{...props}
/>
);
return <div>Loading</div>;
}}
/>
);
}
};
And you can use like:
class Test extends React.PureComponent {
render() {
return (
<div>...</div>
);
}
}
export default withData(Test, {
query: graphql`
query user {
name
}
`,
variables: {
key: 'value',
},
});
I think the result why you can not use react-relay-network-modern-ssr
in next.js
is that react
does not handle async/await
in the component. I had printed the response in react-relay-network-modern-ssr
, and I found the data is in the cache. The response printed after the component render.
As a result, react-relay-network-modern-ssr
did not work.
I add the simple network which does not need to wait async/await
. This environment give data to QueryRenderer
directly. So, it can work in the server side.
This is the version that can work in _app.js
with next.js
, and this does not use withData
.
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { QueryRenderer } from 'react-relay';
import NextApp, { Container } from 'next/app';
import { environment, createEnvironment, relaySSR, } from './createEnvironment';
export default class App extends NextApp { static getInitialProps = async ({ Component, router, ctx }) => { const { variables } = (await Component.getInitialProps?.(ctx)) || {};
if (environment)
ReactDOMServer.renderToString(
<QueryRenderer
environment={environment}
query={Component.query}
variables={variables}
render={() => null}
/>,
);
return {
variables,
relayData: await relaySSR?.getCache(),
};
};
render() { const { Component, variables = {}, relayData } = this.props;
return (
<Container>
<QueryRenderer
environment={createEnvironment(relayData)}
query={Component.query}
variables={variables}
render={({ error, props }) => {
if (error) return <div>{error.message}</div>;
else if (props) return <Component {...props} />;
return <div>Loading</div>;
}}
/>
</Container>
);
} }
and write page like this:
```js
export default class Test extends React.PureComponent {
static getInitialProps = async () => ({
variables: {
key: 'value',
},
});
static query = graphql`
query user {
name
}
`;
render() {
return (
<div>...</div>
);
}
}
Hope to help you 😸 .
@HsuTing Your solution did really good help for me! Thanks for sharing :)
@WDFS Thank you ^^
I had added an example in next.js
, if someone need a example project, you can see here.
@HsuTing that example actually doesn't work.
@d3vhound Did you get any error?
You can try to downgrade react-relay
to 1.5.0
. After I tested, I think this is something wrong when using react-relay-network-modern
with react-relay >= 1.6.0
.
@HsuTing I'm using your example and it works perfectly now. One issue I'm having though is that the server initially renders the page with stale cached data before fetching the fresh data over the network on the client. Is there any way I can disable this cache and force the server to render the page with network data? Something similar to dataFrom={"NETWORK_ONLY"}
in QueryRenderer
?
@stan-sack Sorry, we can not do it, now. React SSR
can not use async/await
. This is the reason why we get the data
and set the data
in cache
before rendering. As a result, when we render the component, we do not request the data from the network and we can get data in QueryRenderer
. If we do not use cache
, QueryRenderer
will need to wait the request working. However, React
will not block the rendering before request done. As a result, you will never get the data in SSR
. That is why we can not use QueryRenderer
with SSR
directly.
The another important thing is that we do not use the cache in QueryRenderer
. In react-relay-network-modern-ssr
, we make the cache in react-relay-network-modern-ssr
, not QueryRenderer
, and return the cache when QueryRenderer
request the data from the network. The default value of dataForm
is NETWORK_ONLY
in QueryRenderer
.
On the other hand, React
is available to use lazy loading
. However, it can only use in the client side. After lazy loading
is available to use in the server side one day, we will not need to to cache the data before rendering. You can see here to learn the more information.
Ah. Thankyou for the explanation!
I think the result why you can not use
react-relay-network-modern-ssr
innext.js
is thatreact
does not handleasync/await
in the component. I had printed the response inreact-relay-network-modern-ssr
, and I found the data is in the cache. The response printed after the component render. As a result,react-relay-network-modern-ssr
did not work.
Is that why the react-relay-network-modern-ssr
example repo in nextjs is taken off? I started a project couple of weeks ago and the repo was there. Now I am planning to start a new one that I will actually work on but I am not sure if I should just copy my package.json from the other project or not.
Yes. If you have any problem about react-relay-network-modern-ssr
in next.js
, I think you can try to fix this or open a issue in next.js
.
pretty sure, that the problem here is solved with this PR https://github.com/facebook/relay/pull/2883
When it comes to priming the cache of react-relay-network-modern, this middleware does a fantastic job. But what i'm running into currently is that nothing is actually rendered server side because the relay store is populated AFTER .getCache() which happens AFTER renderToString or whatever SSR implementation there is. I'm using next.js.
The result of this:
is that the html sent to the client is 'loading...' -- of course once the middlware kicks in and client side query returns immediately from cache it renders quickly but its not really rendering what is expected on the server.
Thoughts?