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
Take a look at Immutable Web Apps!
It is a methodology that creates a separation of concern between
index.html
and all other static assets:index.html
as a deployment manifest that contains all environment-specific values.This is similar to the accepted answer, by including the environment variables directly in the
index.html
It also requires that the reference to other static assets are unique and versioned.
It honors both the "build, release, run" and "config" principles of 12factor.
A great benefit of this approach is that it enables atomic live releases by simply publishing
index.html
.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 theindex.html
add:That
env.js
script can set the API_ROOT on the window:And
agent.js
can check for thewindow.env.API_ROOT
else default: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
elseindex.html
:To get that to work the start script in the
package.json
is simply:Then everything works. If
API_ROOT
is defined in environment variables then theserver.js
will generate it onwindow.env
and theagent.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.
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