Rendering an environment variable to the browser in a react.js redux production build running in different environments
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.
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
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:
- It treats
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
window.env={'API_ROOT':'https://conduit.productionready.io/api'}
It also requires that the reference to other static assets are unique and versioned.
- It treats javascript bundles as immutable assets that are built once, published once, and used in multiple environments. Allowing the assets to be promoted through environments to production without being modified or moved.
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
.