Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rendering an environment variable to the browser in a react.js redux production build running in different environments

The readme of the react redux realworld.io application at https://github.com/gothinkster/react-redux-realworld-example-app says to edit the src/agent.js to change the API_ROOT to point to a different backend api instance. We want to set things up so that API_ROOT can be defined by an environment variable that is different within the multiple environments (e.g., “staging” and “live”) where we run the production build.

We are running in containers on openshift kubernetes following 12factor.net principles where the code is built once then promoted through environments. We can spin up new environments with a single command so we don’t want to have a switch statement within the code that names each environment and hardcodes the backend API_ROOT for each environment. Instead, I want to be able to run an existing production build container image in a fresh environment using an environment variable change the API_ROOT to point to the correct backend API we want to test against.

I have looked at a number of different blogs, stackoverflow answers and the official documentation. The main problem is that typical solutions “bake in” the process.env.API_ROOT environment variable at build time else have a switch that hardcodes the details of all environments into the code. Neither of which are satisfactory as we want to able to take the latest stable code in an existing container and run it in a new environment using the API running there.

The closest I have got so far is to edit the code to render the process.env.API_ROOT into a <script> tag that sets it on a window.API_ROOT variable. Then check whether that exists else use a default when defining the const for API_ROOT. This feels very invasive and a bit fragile and it is not clear to me where is the best place to render such a script tag in the sample app at https://github.com/gothinkster/react-redux-realworld-example-app

like image 838
simbo1905 Avatar asked Apr 23 '18 07:04

simbo1905


People also ask

How do you dynamically change your react environment variables without re building?

react-inject-env: a tool that allows you to modify environment variables after the static file has been built. The short and simple explanation is that it creates an env. js file in the /public folder. The file is then executed at the start and the variables are loaded into the window object.

How do you access system environment variables in react JS?

There's only one gotcha, to access environment variables from the client app they must be prefixed with REACT_APP_ . Otherwise they will only be accessible on the server side. You can access environment variables (with the REACT_APP_ prefix) from your React app via the Node. js process.


2 Answers

Issue #578 of react-create-app has a good answer. tibdex suggested using a public/env.js that is generated with the correct properties then in the index.html add:

 <script src="%PUBLIC_URL%/env.js"></script>

That env.js script can set the API_ROOT on the window:

window.env={'API_ROOT':'https://conduit.productionready.io/api'}

And agent.js can check for the window.env.API_ROOT else default:

function apiRoot() {
  if( window.env.API_ROOT !== 'undefined') {
    return window.env.API_ROOT
  }
  else {
    return 'https://conduit.productionready.io/api'
  }
}

const API_ROOT = apiRoot();

Exactly how that file is created from an environment variable he doesn't describe but I was able to have the npm start command generate it.

Moorman then suggested simply writing an express server that serves that /env.js else index.html:

const express = require('express');
const path = require('path');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));

const WINDOW_ENV = "window.env={'API_ROOT':'"+process.env.API_ROOT+"'}\n";

app.get('/env.js', function (req, res) {
  res.set('Content-Type', 'application/javascript');
  res.send(WINDOW_ENV);
});

app.get('/*', function (req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(process.env.PORT);

To get that to work the start script in the package.json is simply:

"start": "PORT=8080 node server.js",

Then everything works. If API_ROOT is defined in environment variables then the server.js will generate it on window.env and the agent.js will use it.

update I set a cache time of five minutes on env.js with res.setHeader("Cache-Control", "public, max-age=300"); as the setting is rarely going to change.

update I read a lot of confusion around this topic and people answering it along the lines of ”change your workflow to align to the defaults of the tools”. The idea of 12-factor is to use a workflow that is established as best practice that the tools should follow, not vice-versa. Specifically a tagged production ready container should be configurable by environment variables and promoted through environments. Then it's "the same thing" that is debugged and tested that runs in live. In this case of a single page app it requires that the browser makes a trip to the server to load the environment variables rather than baking them into the app. IMHO this answer is a straightforward and simple way of doing that to be able to follow 12-factor best practices.

update: @mikesparr gives a good answer to this problem at https://github.com/facebook/create-react-app/issues/982#issuecomment-393601963 which is to restructure the package.json to do the webapp work of generating the SPA upon start up. We took this approach as a tactical workaround. We are using a saas openshift kubernetes that charges for memory. Building our react app with webpack needs 1.2Gb (and rising!) So this approach of moving the npm build to the container startup command we need to allocate 1.2Gb to every pod we start which is a significant amount of additional costs for a single page app whereas we can get away with 128MB as the memory allocation when the app is precompiled. The webpack step is also slow as it is a large app. Building every time we start up the app slows down rolling deployments by many minutes. If a VM crashes and kubernetes starts replacement containers on a new VM it takes minutes to start up. A precompiled app starts in a few seconds. So the solution of "webpack at startup" is not satisfactory in terms of resource consumption and speed for real business application that are tens of thousands of lines of code. IMHO this answer of fetching a configuration script from the server is superior.

like image 176
simbo1905 Avatar answered Sep 18 '22 08:09

simbo1905


You can replace the environment variables directly in your index.html file exposing a global ENV variable. That replacement needs to be done at runtime to make sure that you have a portable image that you can run in different environments.

I have created an example repository here https://github.com/axelhzf/create-react-app-docker-environment-variables

like image 35
axelhzf Avatar answered Sep 17 '22 08:09

axelhzf