How to handle server-side rendering in React

Max Rozen

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.


[Use Next.js](

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


[Use 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:
  2. Adding Apollo/GraphQL:
  3. Adding Styled-Components:
  4. Adding Material-UI

Do you struggle to keep up with best practices in React?

I send a single email every two weeks with an article like this one, to help you keep track of what's happening in the React ecosystem.

Lots of developers like them, and I'd love to hear what you think as well. You can always unsubscribe.

    Join 1,555 React developers that have signed up so far!