Skip to content

relay-tools/react-relay-network-modern-ssr

Repository files navigation

SSR middleware for react-relay-network-modern (for Relay Modern)

npm Travis Commitizen friendly semantic-release FlowType compatible

For a full examples, see:

Server

import express from 'express';
import ReactDOMServer from 'react-dom/server';
import { RelayNetworkLayer } from 'react-relay-network-modern';
import RelayServerSSR from 'react-relay-network-modern-ssr/lib/server';
import serialize from 'serialize-javascript';
import { Environment, Network, RecordSource, Store } from 'relay-runtime';
import schema from './schema';

const app = express();

app.get('/*', async (req, res, next) => {
  const relayServerSSR = new RelayServerSSR();

  const network = new RelayNetworkLayer([
    // There are three possible ways relayServerSSR.getMiddleware() can be used;
    // choose the one that best matches your context:

    // By default, if called without arguments it will use `fetch` under the hood
    // to request data. (See https://github.com/relay-tools/react-relay-network-modern
    // for more info)
    relayServerSSR.getMiddleware(),

    // OR, you can directly pass in a GraphQL schema, which will use `graphql`
    // from `graphql-js` to request data
    relayServerSSR.getMiddleware({
      schema,
      contextValue: {},
    }),

    // OR, if you need to prepare context in async mode, `getMiddleware` will also
    // accept a function:
    relayServerSSR.getMiddleware(async () => ({
      schema,
      contextValue: await prepareGraphQLContext(req),
    })),
  ]);
  const source = new RecordSource();
  const store = new Store(source);
  const relayEnvironment = new Environment({ network, store });

  // Once the RelayEnvironment is instantiated, two App renders need to be made in
  // order to prepare data for hydration:

  // First, kick off Relay requests with an initial render
  ReactDOMServer.renderToString(<App relayEnvironment={relayEnvironment} />);

  // Second, await while all data were recieved from graphql server
  const relayData = await relayServerSSR.getCache();

  // Third, render the app a second time now that the Relay store has been primed
  // and send HTML and bootstrap data to the client for rehydration.
  const appHtml = ReactDOMServer.renderToString(
    <App
      relayEnvironment={new Environment({
        network: Network.create(() => relayData[0][1]),
        store,
      })}
    />
  );

  try {
      res.status(200).send(`
      <html>
        <body>
          <div id="react-root">${appHtml}</div>
          <script>
            window.__RELAY_BOOTSTRAP_DATA__ = ${serialize(relayData)};
          </script>
          <script src="/assets/bundle.js"></script>
        </body>
      </html>
    `);
  } catch (error) {
    console.log('(server.js) Error: ', error);
    next(error);
  }
}

app.listen(3000);

// simple example, how to asynchronously prepare data for GraphQL context
async function prepareGraphQLContext(req) {
  const { userToken } = req.cookies;
  const user = userToken ? (await somehowLoadUser(userToken)) : undefined;
  return {
    user,
    req,
  }
}

Client

import { RelayNetworkLayer } from 'react-relay-network-modern';
import RelayClientSSR from 'react-relay-network-modern-ssr/lib/client';

const relayClientSSR = new RelayClientSSR(window.__RELAY_BOOTSTRAP_DATA__);

const network = new RelayNetworkLayer([
  relayClientSSR.getMiddleware({
    lookup: true // Will preserve cache rather than purge after mount.
  }),
]);

...

ReactDOM.render(
  <QueryRenderer
    dataFrom="STORE_THEN_NETWORK" // Required for Relay 1.5
    environment={environment}
    ...
  />,
  document.getElementById('react-root')
);

Contribute

I actively welcome pull requests with code and doc fixes.

CHANGELOG

License

MIT