How to handle server-side rendering in React

The first time I worked through deploying a server-side rendered React app to AWS Lambda, it took me about 5 hours of messing around with my Terraform config, and actual server-side code before I figured out what was wrong (My terraform config was for a outputting JSON from my API Gateway, rather than HTML).

While the easiest way to get started with server-side rendering in React is to start your project with it in mind, I realise for 95% of people this is not an option.

So, you have two options that will save you HOURS figuring out how to setup babel and webpack to play nicely together.

Either:

[Use Next.js](https://nextjs.org/).

Next.js solves server-side rendering of React applications with a framework approach. Similar to using Gatsby. If you have a site with content that you want indexed on search engines, and you're fine with learning a new framework, then you probably want to look into Next.js

OR

[Use Razzle](https://github.com/jaredpalmer/razzle).

It's basically Create-React-App, for server-side rendered Javascript.

It has an extensive list of examples including: AfterJS, Elm, Firebase Functions, Inferno, JSX, Material-UI, Koa, Preact, React Native Web, Redux, Styled Components and TypeScript.

Once you've seen some Razzle examples, and added Razzle to your project (or just created a fresh Razzle project and copied all of your components into it), you're ready for the next steps.

Usually all you need to do is render some React components, and inject them as Strings into a HTML template. Easier said than done, but if you take baby steps (for example, start with rendering your CSS) you'll find it much more achievable than attempting to refactor your entire Application to be Server-side rendered in one go (See Indispensible resources below for the resources I used).

Step 1: Refactor all of your CSS-modules into CSS-in-JS.

Using either Emotion or Styled-Components. You should avoid global CSS if you're using Material-UI, though Styled-Components/Emotion are more forgiving if you modify existing components via global CSS.

Step 2: Refactor your Redux.

In my case I only needed to call createStore on the server like this:

import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducer from './reducers';
export default (initialState) =>
createStore(reducer, initialState, applyMiddleware(thunkMiddleware));

Where the reducer was my client-side reducer code. See this link for further info.

Step 3: Add Apollo/GraphQL

The trick to making this work in my case was adding ssrForceFetchDelay: 100 to my Apollo Client (This prevents Apollo from refetching GraphQL queries after the server already fetches them). Follow the tips at Resource [2] below.

Step 4: Deploying a bundle without having to copy your node_modules/ folder into your AWS Lambda function

This one was surprisingly easy, and at the time not particularly well documented: Create a razzle.config.js file with the following contents:

module.exports = {
modify: (config, { target, dev }, webpack) => {
// do something to config
const appConfig = config; // stay immutable here
if (target === 'node' && !dev) {
appConfig.externals = [];
}
return appConfig;
},
};

Razzle by default will use nodeExternals to prevent webpack from bundling your node_modules - which you need for AWS Lambda. This fixes that issue.

Indispensable resources:

  1. Getting started: https://medium.freecodecamp.org/demystifying-reacts-server-side-render-de335d408fe4
  2. Adding Apollo/GraphQL: https://dev-blog.apollodata.com/how-server-side-rendering-works-with-react-apollo-20f31b0c7348
  3. Adding Styled-Components: https://www.styled-components.com/docs/advanced#server-side-rendering
  4. Adding Material-UI https://material-ui.com/guides/server-rendering/

(Shameless plug for the useEffect book I wrote below)

Tired of infinite re-renders when using useEffect?

A few years ago when I worked at Atlassian, a useEffect bug I wrote took down part of Jira for roughly one hour.

Knowing thousands of customers can't work because of a bug you wrote is a terrible feeling. To save others from making the same mistakes, I wrote a single resource that answers all of your questions about useEffect, after teaching it here on my blog for the last couple of years. It's packed with examples to get you confident writing and refactoring your useEffect code.

In a single afternoon, you'll learn how to fetch data with useEffect, how to use the dependency array, even how to prevent infinite re-renders with useCallback.

Master useEffect, in a single afternoon.

useEffect By Example's book cover