Adding Server Side Rendering to a Relay Production App
Tags: relaygraphqlssr
Published: 2020-02-27 13:21:38

---
title: Adding Server Side Rendering to a Relay Production App
published: true
description: adding server side rendering to a Relay production App can be a little bit tricky
tags: relay;graphql;ssr; 
---

# Reality
Reality has a surprising amount of detail (https://johnsalvatier.org/blog/2017/reality-has-a-surprising-amount-of-detail). Reading all blog posts about Server Side Rendering (SSR) is not enough to properly implement it in a production app. The same is valid for most of the development tasks, like setting up a react native project, fixing a webpack weird bug and so on. You need to get your hands dirty with "reality" to understand what tiny details that matter.


---

# Our Frontend Stack
A bit of context of this task, we work on Feedback House (https://feedback.house/), a platform to manage teams. One of our modules is a hiring platform, where candidates can apply to a job posting and manage their applications (https://entria.contrata.vc/).
We decided to move this module/frontend to use SSR to improve social media sharing and improve head meta tags.
This frontend uses the following stack: react, styled-components, material-ui, styled-system, loadable-components, react-router, and relay.


---

# SSR "framework"
We checked pure webpack solution, razzle, nextjs, afterjs.
Nextjs won't work well to us, as we have a nested route, and managing persisted layout patterns would be a big change for us (https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/)
Afterjs was too high level, and pure webpack was too low level.
Razzle was in the right spot, razzle is just 2 webpack combined.


---

# Basic Razzle knowledge
*Commands*
razzle start: start your development application (compiles both server and client)
razzle build: build your app to production usage

*Files/Structure*
razzle.config.js: let you bring plugins and modify webpack/babel and other configs
index.js/ts: server entry point - basic HMR 
server.tsx: server itself (express/koa) that will render React app on server
client.tsx: client entry point that will hydrate SSR render


---

# Facing Reality
We started following razzle examples and tutorials, and keep bumping on "issues" that I will describe here.

*Fixing Typescript* 
Razzle has a razzle.config.js config file that lets you config any part of their config (webpack, babel and so on).
my razzle.config.js looks like this:

{% gist https://gist.github.com/sibelius/67f473bd76e47c58a9221343ae27e24a %}


inside webRazzlePlugin with have a modify function to return a custom webpack config
To make webpack transpile typescript .ts and .tsx files with added new extensions like this:

{% gist https://gist.github.com/sibelius/45b76ff9ab9693184be784868b667459 %}


We also had to remove `strictExportPresence` webpack config (https://webpack.js.org/configuration/module/#module-contexts), so import types won't cause compilations errors, just warnings

*Fixing Monorepo*
We modify babel loader to transpile all monorepo packages to let us modify any package and reload our main frontend:

{% gist https://gist.github.com/sibelius/232986fd944620d94b6c34e963c62fa4 %}

*Fixing .env*
We let our environment variable inside .env files to make it easy to build on CI. We added dotenv-webpack to make this possible:
{% gist https://gist.github.com/sibelius/a85b79e0720ab6795fe01e9ef7a2c3ec %}

# First SSR Render
The first SSR render was just a loading component in an html file \o/

{% gist https://gist.github.com/sibelius/d0c29d4fdccb3ef7beaab0917973df11 %}

Just calling renderToString is not enough to properly render a complex app

*Fixing React-Router*
We use a static route config (https://github.com/ReactTraining/react-router/tree/master/packages/react-router-config). And we use nested route to handle persistent layout, routed tabs, routed modal and more.
You need to use StaticRouter on server and BrowserRouter on the client to make them work well:

{% gist https://gist.github.com/sibelius/fa7868dd65a1543fdeff7786eb6dd3f5 %}

client:

{% gist https://gist.github.com/sibelius/1a92efba433e42bcd468d2decb03b2df %}

StaticRouter will set context.url if there is some redirects while rendering

*You gonna need Extractors*
styled-components, material-ui and loadable-components, all have "extractors". They will collect styles and chunks to be added to your SSR render

{% gist https://gist.github.com/sibelius/efb0d817c58567c07209c54acbaf4c5c %}


Without this your first render won't work well, your components won't be styled correct, and code split won't work out of the box.


---

## Checkpoint
After all this work, we expect to have at least a better first render experience. However, SSR is harder than it sounds. After all this, we still have a simple <Loading /> component \o/.

*Fixing Relay (Data Fetching)*
We need to fetch and store all GraphQL queries before rendering our components.
I've followed these 2 examples to make this possible (https://github.com/jaredpalmer/react-router-nextjs-like-data-fetching and https://github.com/relayjs/relay-examples/tree/master/issue-tracker)
IssueTracker example uses preloadQuery + usePreloadQuery that required react and relay experimental builds, and our production code still can't move to it, as we still need to fix some StrictMode issues. So we have a mixed approach.
The first "tricky" is to colocate query and variables on each route, like this:

{% gist https://gist.github.com/sibelius/cf29c147a1d25d0194386a6be39d0583 %}

require __generated__ is the same as using graphql`` tag

{% gist https://gist.github.com/sibelius/d31b86b5e14615cafff6546de90e16cc %}

queriesParams will get variables based on match params.
query lets us fetch route data dependencies before rendering the component, this also lets us prefetch code and data and follow render-as-you-fetch Reat pattern.

*Prefetching Relay queries per route*
We use matchRoutes from react-router-config to find which routes have matched, it can be more than one, as we have nested routes.
After that we fetch all queries using relay fetchQuery (https://relay.dev/docs/en/fetch-query):

{% gist https://gist.github.com/sibelius/f55ac3493ea52d9dd821f9d8042bff34 %}

## Rendering with Relay store data

{% gist https://gist.github.com/sibelius/850b3c4a78fbc212409a67b0aad77e62 %}

All queries made using fetchQuery will store data on a Relay Environment, and we going to use it on our RelayEnvironmentProvider
The trick here is to always use the correct fetchPolicy store-and-network (https://relay.dev/docs/en/query-renderer#props) on QueryRenderer, so it will reuse Environment data, instead of sending another request.

*Make RelayEnvironment work on both client and server*
{% gist https://gist.github.com/sibelius/02f50e9bb4fdb6f79b33d9dad2193819 %}

When on the server, we create a new Relay Environment per request, so we don't leak user data to another user. On the client we reuse the Environment and start the store using some records if available.
We store all Relay Store records inside window.__RELAY_PAYLOADS__, so we hydrate Relay store on the client and avoid request the same data again.

{% gist https://gist.github.com/sibelius/d2af99af6d4bee9789498a8a8f72c669 %}


On the client with create Relay Store like this:

{% gist https://gist.github.com/sibelius/b91471eed37071a9bdac82542136a9b9 %}

After the use of __RELAY_PAYLOADS__ we remove it from the window.

---

## Where we are?
After all this work, we have a nice first render without loading.
However, this is not enough, as we have some private routes that needs also to be rendered properly.
Fixing authentication (localstorage)
Most client-side apps using localstorage to manage authentication as session storage looks like a bunch of work, and cookies look like an outdated and "complex" solution.
However, when you want to SSR authed routes you can't rely on localstorage, as it does not work on the server.
The first thing to do, it to make your GraphQL server set authentication cookies httpOnly after a login/signup process:

{% gist https://gist.github.com/sibelius/8aa078412d1bfa4094d1f96acb63d7e1 %}


After this, you need to modify your Relay Network Layer (https://medium.com/entria/relay-modern-network-deep-dive-ec187629dfd3) fetch call, to use credentials: 'include'. This will send cookies to your server automatically (magic), but it will break if your frontend and server is on different domains (dammit CORS).
You can fix CORS, using a proxy on your SSR server to "fake" that your GraphQL server is in the same domain as your frontend. On production, you can use nginx to fix this.

## ExecuteEnvironment
ExecuteEnvironment will help you check if you are running code on server or client:

{% gist https://gist.github.com/sibelius/09a86816326b630e222c84fb8d7291ed %}


This is the same/similar to Relay ExecuteEnvironment codebase.
Fixing hostname
To fix some isomorphic problems like checking what is the hostname, I've come up with a global.ssr that contains some helpers:

{% gist https://gist.github.com/sibelius/2f1e58a2b362012889187c284750fb5e %}

After that we can have an isomorphic getDomainName like this:

{% gist https://gist.github.com/sibelius/5cb125cc24f16eaa51c41b8206318d25 %}


---

# After Thoughts
Is that all folks? I don't think so, there are still some issues that need to be solved to improve this SSR approach.
decide to render a mobile or a desktop version on SSR
fixing Head tags and react-helmet (https://twitter.com/sseraphini/status/1232726960494780416), it looks like this is not so easy in React \o/
useIsClient hook to defer from rendering only to client-side

{% gist https://gist.github.com/sibelius/65693a8e3fda3e5bd39a3ec0c4343a08 %}


use @defer to avoid fetching too much data on server
check new React streaming api



---

This write has not all the details, ping me on twitter to discuss more it (https://twitter.com/sseraphini)
You can also learn more about relay using my open-sourced course (https://github.com/sibelius/relay-modern-course)
You can watch me demo some cool Relay features to React Europe here https://twitter.com/ReactEurope/status/1226951417002446849, you can play with the demo here https://react-europe-relay-workshop.now.sh/
If you wanna more hands-on on Relay, check to React Europe Relay Workshop (https://twitter.com/reacteurope/status/1194908997452795904?s=21), I'll show all this and more advanced Relay patterns.

medium version: https://medium.com/@sibelius/adding-server-side-rendering-to-a-relay-production-app-8df64495aebf?postPublishedType=repub