Relay and SSR using createOperationDescriptor
Tags: react, relay, ssr
Published: 2019-12-19 04:50:58

tldr;
Use [`createOperationDescriptor`], and ask [`RelayEnvironment`] for the data, rather than sending queryProps to a window object.

```jsx
const Component = () => {
	const environment = getRelayEnvironment(records);
	const queryConcreteRequest = getRequest(someGraphQLQuery);

	return (<SomeQueryAskingComponent {...environment.lookup(
		createOperationDescriptor(
			queryConcreteRequest,
			someVariables,
		).fragment,
	).data}/>);
};
```

A lot of what I see online in terms of articles around Relay + SSR is that they all follow a pattern similar to this:

- They have a page level query ✅
- The page level query gets fetched on the server with a [`fetchQuery`] ✅
- They then dump the store to a window object (for hydration) ✅

But where they all fall apart is when they also flush the queryProps to the window object that you'd typically give to the component. ❌

Before I tell you why that is wrong, lets look at the flow of how things work.

Relay has 2 parts really, you have a [`RelayEnvironment`], and a query (fragments, query renderes, etc..). The environment lives in a [`RelayEnvironmentProvider`] so when you have a [`useFragment`] or [`createFragementContainer`] it creates an identifier, and resolves data from its props. Those props typically come from a [`queryRenderer`]'s `render` prop, or in SSR world come directly from a [`fetchQuery`].

So the reason why the above is wrong is because if you have a massive page level query. The store is flushed to the window object, as well as the query props! Both contain duplicated bits of data. One being the map of [`ID`]'s, and one being the "resolved data" for your query.

Now, in a production app using Next.js as the example, there is a [`NEXT_DATA`], which is basically the window object constructed as a way for server side to relay (pun intended) initial props to the client side, for hydration. Now if you flushed both you end up with massive payloads. As an example, I had a blog that asked for authors, article bodies, tags, comments, reviews, related article's etc... All of that rolled into something like 46k lines of json (please just accept that it was large), which is horrible right! 

Now lets get to the point of the article - How to fix this!

Instead of flushing the queryProps in the [`NEXT_DATA`]. Just _figure out a way_ to resolve the queryProps on the client using nothing but the store. It's simple really.

You need 2 things: a reference to the query itself, and a [`RelayEnvironment`]. The query forms something of an "id" into the store, and the environment has the store. So create a relay store identifier through a [`createOperationDescriptor`], which takes the query and its variables and spits out an id. Then use that id to lookup the data in the store, and simply give that to the component. Happy days! And now you're left with, and from our example, an almost 1k line JSON. Some ridiculous savings there!

Here is an example of this:

```jsx
import { fetchQuery, graphql } from 'react-relay';
import { createOperationDescriptor, getRequest } from 'relay-runtime';

const WithData = (PageComponent, options) => {

	const WrappedComponent = ({ variables, records }) => {
		const environment = getRelayEnvironment(records);
		const queryConcreteRequest = getRequest(options.query);
		const requestIdentifier = createOperationDescriptor(
			queryConcreteRequest,
			variables,
		);

		const pageData = environment.lookup(
			requestIdentifier.fragment,
		);

		return <RelayEnvironmentProvider environment={environment}>
			<PageComponent {...pageData.data}/>
		</RelayEnvironmentProvider>;
	};

	WrappedComponent.getInitialProps = async () => {
		const environment = getRelayEnvironment();

		const variables = options.variables();

		await fetchQuery(
			environment,
			options.query,
			variables,
		);

		const records = environment
			.getStore()
			.getSource()
			.toJSON();

		return {
			variables,
			records,
		};
	};

	return WrappedComponent;
};

export default withData(
	({ article }) => <h1>{article.name}</h1>,
	{
		query: graphql`
                    query ArticleQuery($slug: String!) {
                        article(slug: $slug) {
                            name
                        }
                }`,
		variables() {
			return { slug: 'example' };
		},
	});


```

[`createFragementContainer`]: https://relay.dev/docs/en/fragment-container
[`useFragment`]: https://relay.dev/docs/en/experimental/api-reference#usefragment
[`fetchQuery`]: https://relay.dev/docs/en/fetch-query
[`queryRenderer`]: https://relay.dev/docs/en/query-renderer
[`RelayEnvironmentProvider`]: https://relay.dev/docs/en/experimental/api-reference#relayenvironmentprovider
[`ID`]: https://facebook.github.io/relay/graphql/objectidentification.htm
[`NEXT_DATA`]: https://github.com/zeit/next.js/blob/56c1392bd65f1c0e897e8c50afdaebb307d955a7/packages/next/next-server/lib/utils.ts#L148
[`RelayEnvironment`]: https://relay.dev/docs/en/relay-environment
[`createOperationDescriptor`]: https://github.com/facebook/relay/blob/4b5ce66bf490697e752dcee3d2b5d64672d47714/packages/relay-runtime/store/RelayModernOperationDescriptor.js#L35