Hosting a Create-React-App (CRA) website

From Luis Gallego Hurtado - Not Another IT guy
Jump to: navigation, search


Some context

As part of my adventure as entrepreneur, I created a web app in Javascript, with ReactJS. In order to boost development, I decided to use Create React App (CRA). It was on version 1 when I started using it, but it paid off quickly.

I had been developing my web app with Create React App (CRA) for a while, using local environment in localhost for testing, so I started looking for a way to host it

Some technical details about the website

Pure Create React App

In middle 2019, my web app had been developed with Create React App. It was a SPA developed respecting the CRA guidelines, so I had not intention to eject it, so far.

Heterogenous routing

Most of my app used client routing, with React Router and React Router Dom using browserHistory.

Those pages, displayed a spinner when configuration had not been loaded yet from REST API. Otherwise, displayed content, according to configuration.

Other parts of the app used static routing, dispatching static content with ReactJs.

There were also some static resources (like robots.txt), which should be reachable.

SEO: Dynamic HTML meta tags in every page I had different HTML meta tags in every page, depending on content, which were injected using React Helmet.

Which sort of hosting to use? PaaS, IaaS?

In order to select PaaS or IaaS, I took into consideration the following points:

  • Our technical capacity was wery limited. Basically one single person was implementing web app, mobile apps, backend platform, database, and doing much more tasks related to Startup.
  • Experience with IaaS and cloud was limited at the time we were making the decision.

We went therefore for PaaS providers.

PaaS: extra bonuses

  • HTTP with autorenewal of certificates
  • CDN
  • Pipeline for deploying new versions
  • Support for different environments

Considering proposed platforms at CRA documentation

CRA documentation described deployment process with several providers.

Regarding Azure, Firebase, S3 and CloudFront, in this stage, we were not considering big products like Azure, Google Cloud products or AWS products. We preferred some third party product, specific on hosting web apps through CDN with minimal configuration, and since our experience with big platforms was limited, we actually wanted simplicity at a fix cost, much more than flexibility with dynamic pricing.

First versions: Heroku

I developed first versions of my app in Heroku.

In that moment, I had only client routing with React Router and React Router Dom using browserHistory, and web worked as expected.

Everything changed when I started using React Helmet, to inject different HTML meta tags in every dynamic page, so I could have better SEO ranking and I added static routing for certain javascript pages (with ReactJS).

Somehow, Helmet was not injecting HTML meta tags properly for every page.

Next approach: Surge

In order to use Surge, as Surge did not support client side routing, I had to pre-render my CRA app.

After trying react-snapshot and react-snap, I could generated all static pages only react-snapshot, but even after setting up a generous snapshotDelay parameter, it look like static pages generation didn’t play well with the Spinner and loading of configuration from REST API, so all pages were rendered with infinite spinner.

I realized in this point that it could be really hard to setup static server rendering with app, so I had to look for a provider that supported client side rendering. Therefore, GitHub Pages could not be considered since it does not support client side routing. Additionally, there was not mention in Now documentation to client side routing for Javascript apps, so I moved on.

In addition, Surge did not fit as it did not serve my robots.txt file or other static resources

First success deployment: Netlify

Netlify was really awesome, since native support for client side routing worked just by adding _redirects file with the index.html redirection.

Just adding a .netlify file with my site id for my build folder, everything worked as expected.

I kept netlify for a long time, but there was something missing: multiple environments.

Netlify and password-protected stage environment I had foreseen an scenario, before the first release of the product, with the following features:

  • Production environment with some specific content
  • Stage environment, password-protected with the current web app.

Nelify did not support multiple environments, but at least, I could password-protect my website, so it could be available as a stage pre-release environment, available online to rest members of the team (mostly my marketing partner).

Netlify pricing strategy However, by the time I was using it, free tier didn’t allow me to password-protect the website, and I had to upgrade plan and pay $300 a month to get it. In addition, $300 a month was quite a expensive cost for a website of the Startup, since early stages of product wouldn’t bring enough income to make it affordable.

So Netlify pricing strategy was clearly unsuitable for early stage website products.

The ideal hosting: Aerobatic

It was long time before I found Aerobatic. There was not free tier but a 30 days tryial, and I had to pay for certificates and custom domain name, but price was as low as $15 a month (versus $300 a month I had to pay to Netlify), so pricing suited much more a Startup web app.

There will be of course more performance considerations to keep in mind, but in that stage, I just had some requirements, a web app to deploy and minimal budget to spend at.

What I got in Aerobatic for $15 a month:

  • 2 environments (called stages in Aerobatic): stage and prod.
  • Password-protected stage environment.
  • Autorenewed Amazon certificated for custom domain.

In order to get that, I performed some changes in my web app:

A new .env.stage file was created, with the environment variables required for a new stage environment. I had then .env (development), .env.stage and .env.production.

Since I could not change node_env variable without ejecting, I just created support for feature flags (new REACT_APP_FEATURE_FLAGS property) so flags were different in stage and production environments.

Finally, I renamed build CRA script as build:production, and I created a new build:stage script with “env-cmd .env.stage npm run build:production”

My package.json

"scripts": {
    ...
    "build:production": "react-scripts build",
    "build:stage": "env-cmd .env.stage npm run build:production",
    ...
},

My Aerobatic deployment code for STAGE environment looked like:

npm run build:stage
aero deploy --stage stage

While my code for Production:

npm run build:production
aero deploy --stage production