Migrating from Create React App to SSR with Razzle
Tags: react, cra, ssr, razzle
Published: 2021-02-25 14:37:13

## Introduction

Not always you, as a software developer, can run away from a project's scope change. Poor requirements gathering can lead you to these situations, and here I'll show how I handled a specific case where I need to change a project created with CRA (Create React App) to support SRR (Server Side Rendering).

At first, I considered [Nextjs](https://nextjs.org/), which is a robust solution for SSR, but the problem was: lots of rewriting would be necessary. Nextjs is a framework, and as so it has its specific way to implement things. The code impact would be big, big enough to make me search for something new and more affordable for my current situation.

So I found [Razzle](https://github.com/jaredpalmer/razzle). As you can read in the Razzle project description, it specifically aims to feel the gap in buy you into a framework 
or setting things yourself.


## Solution

Similar to CRA, Razzle has its own `create-razzle-app`. The first step was simple as:

```
npx create-razzle-app my-app-name
```
I created a new app and throw my app files inside it, but you can merge what was generated into your existing app (although this can be a bit more arduous).

Razzle works like a charm but, although it has a low code impact on the codebase, there is *some* impact already expected because SSR requires some alterations. So here is what I needed to focus on:

  * Routes;
  * Replace what was using js `window` object;
  * Styles.

First, it is necessary to know when you are on the server or on the browser. The helper below was used for this purpose.

```js
export const isServer = !(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
);
```

### Routes

To be able to navigate back/forward previously accessed pages, `history` from [React Router](https://reactrouter.com/web/guides/quick-start) was being used. The following alteration was necessary:

**From**
```js
export const history = createBrowserHistory();
```

**To**
```js
export const history = isServer
  ? createMemoryHistory({
      initialEntries: ['/'],
    })
  : createBrowserHistory();
```
Using the `createBrowserHistory` function in the server throws you the error `Invariant failed: Browser history needs a DOM`. Obviously, no DOM is available there, so we used the `createMemoryHistory` function that doesn't require a DOM. 

### Replacing the `window` object functions

The `window` object was being used in some parts of the code where the `localStorage` was being called. The `localStorage` was being used to store login sessions and a shopping cart id, so the first step was to find a replacement for it: cookies.

Cookies can be accessed by the server, and although I didn't need to do so, it wouldn't break the app (what otherwise would happen using the `window` object). [React Cookies](https://www.npmjs.com/package/react-cookies) filled my needs, and I encapsulated all my cookies interaction in a class I called `CookieUtility`.

Replacing `localStorage` with my `CookieUtility` solved the question here, and I wanna show the only one that was tricky at first: the `PrivateRoute` component. So the alteration was:

**From**

```js
...

const PrivateRoute = (props) => {
  const token = localStorage.getItem(BrowserStorageKeyEnum.Jwt);
  let isTokenExpired = false;

  if (token) {
    const decodedJwt = jwt.decode(token);
    const currentTimeInSeconds = moment(Math.floor(Date.now() / 1000));
    const expirationTimeInSeconds = decodedJwt.exp - currentTimeInSeconds;

    if (expirationTimeInSeconds <= 0) isTokenExpired = true;
  }

  if (token && !isTokenExpired) {
    return <Route {...props} />;
  } else {
    return (
      <Redirect
        to={{
          pathname: RouteEnum.Login,
          state: { from: props.location }
        }}
      />
    );
  }
};

...
```

**To**

```js
...

export default function PrivateRoute(props) {
  if (isServer) return <LoadingPageIndicator isLoading={true} />;
  else {
    const jwt = CookieUtility.getJwt();

    if (!!jwt) {
      return <Route {...props} />;
    } else {
      return (
        <Redirect
          to={{
            pathname: RouteEnum.Login,
            state: { from: props.location },
          }}
        />
      );
    }
  }
}
```

Keep in mind that that the new version of the `PrivateRoute` is more succinct because the code was refactored, and all the time-wise logic was put in the `CookieUtility`, defining cookies expiration time. 

What you should pay attention to is the first line of the new `PrivateRoute` component function: if in the server, just display a loading indicator. If you are doing so for SEO (Search Engine Optimization) purposes, this would be a problem, but in my case, no private route exists whit this intention, just public ones, so this trick works just fine.

### Styles

The app was being implemented using [Styled Components](https://styled-components.com/) that already comes with an integrated solution for SSR, allowing you to load all the required styles for the target page and put it at the end of your `<header>` tag in the `server.js` generated by Razzle.

```js
import { ServerStyleSheet } from 'styled-components';

...

server
  .disable('x-powered-by')
  .use(express.static(process.env.RAZZLE_PUBLIC_DIR))
  .get('/*', (req, res) => {

const sheet = new ServerStyleSheet();
const styleTags = sheet.getStyleTags();

...

res.status(200).send(
`<!doctype html>
    <html lang="">
    <head>
      <meta http-equiv="X-UA-Compatible" content="IE=edge" />
      <meta charset="utf-8" />
      <title>Welcome to Razzle</title>
      <meta name="viewport" content="width=device-width, initial-scale=1">
      ${assets.client.css ? `<link rel="stylesheet" href="${assets.client.css}">` : ''}
        ${
          process.env.NODE_ENV === 'production'
            ? `<script src="${assets.client.js}" defer></script>`
            : `<script src="${assets.client.js}" defer crossorigin></script>`
        }
        ${styleTags}
    </head>
`
...
```

## Conclusion

This post showed how I migrated from a normal React app created with CRA to an SSR app, using Razzle to accomplish this. It was not done with the intention to work as a tutorial but to show you a path you can follow if you find yourself in the same situation as the one described in the introduction of this post, highlighting the steps that took me some time to understand how to overcome them. 

It was worthed to use Razzle? I definitely would say yes. It was possible to migrate a middle-size app to work with SSR in a short time. The steps I described in the solution section were actually the only ones that forced me to change more large chunks of code, and besides that, I only needed to remove external libs that used the `window` object, but that is expected if you're dealing with SSR (the migration process can be harder depending on how much you relly on those libs).

At the moment this post was written, Razzle is quite an active project, and there are many plugins being developed for it. For instance, there is a plugin you can use to easily handle PWA.

***

This is it! If you have any comments or suggestions, don't hold back, let me know.

***

Options if you like my content and would like to support me directly (never required, but much appreciated):

BTC address: `bc1q5l93xue3hxrrwdjxcqyjhaxfw6vz0ycdw2sg06`

[![buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1n0hai66hv33dphoiic.png)](https://www.buymeacoffee.com/rwehresmann)