You’ve got a React app working locally, but how can you deploy it to different environments?
There’s production, staging, QA, and more… all with their own sets of servers and hostnames and maybe even features that should be enabled or disabled. Plus it still needs to work in development.
Here are a couple ways to do it.
If you can make the assumption that different environments will be accessed by different hostnames in the browser, then this strategy works nicely.
In api-config.js, do something like this:
let backendHost;
const apiVersion = 'v1';
const hostname = window && window.location && window.location.hostname;
if(hostname === 'realsite.com') {
backendHost = 'https://api.realsite.com';
} else if(hostname === 'staging.realsite.com') {
backendHost = 'https://staging.api.realsite.com';
} else if(/^qa/.test(hostname)) {
backendHost = `https://api.${hostname}`;
} else {
backendHost = process.env.REACT_APP_BACKEND_HOST || 'http://localhost:8080';
}
export const API_ROOT = `${backendHost}/api/${apiVersion}`;
Then in your API file (say, api.js), you can import the API URL and you’re off to the races:
import { API_ROOT } from './api-config';
function getUsers() {
return fetch(`${API_ROOT}/users`)
.then(res => res.json)
.then(json => json.data.users);
}
If you’d rather configure the API endpoints at build time, that works too.
If you’re using Create React App, then you’ll have a global process.env
available to get access to environment variables, including process.env.NODE_ENV
, which will be set to “production” after a build.
Additionally, Create React App will only have access to environment variables named starting with REACT_APP_
, so, REACT_APP_SKITTLE_FLAVOR
works but SKITTLE_FLAVOR
will not.
Here’s how that would look with a Create React App build on a Linux/Mac machine:
$ REACT_APP_API_HOST=example.com yarn run build
# The resulting app would have
# process.env.REACT_APP_API_HOST === "example.com"
# process.env.NODE_ENV === "production"
(Windows handles environment variables differently.)
Environment variables can be set to whatever you want. One potential use case is to turn certain features of your app on or off depending on environment. At build time, you can do something like this:
$ REACT_APP_SPECIAL_FEATURE=true yarn run build
# The resulting app would have
# process.env.REACT_APP_SPECIAL_FEATURE === "true"
# process.env.NODE_ENV === "production"
Then you could guard parts of your code by checking that variable. This works from anywhere in your app:
function HomePage() {
if(process.env.REACT_APP_SPECIAL_FEATURE) {
return <SpecialHomePage/>;
} else {
return <PlainOldBoringHomePage/>;
}
}
Variables are Baked In
At the risk of stating the obvious: the “environment variables” will be baked in at build time. Once you build a JS bundle, its process.env.NODE_ENV
and all the other variables will remain the same, no matter where the file resides and no matter what server serves it. After all, a React app does not run until it hits a browser. And browsers don’t know about environment variables.
Unit Testing
Once you’ve got all these variables multiplying your code paths, you probably want to test that they work. Probably with unit tests. Probably with Jest (that’s what I’m showing here anyway).
If the variables are determined at runtime, like in the first example above, a regular import './api-config'
won’t cut it – Jest will cache the module after the first import, so you won’t be able to tweak the variables and see different results.
These tests will make use of two things: the require()
function for importing the module inside tests, and the jest.resetModules()
function to clear the cache.
beforeEach(() => {
jest.resetModules();
delete process.env.REACT_APP_BACKEND_HOST;
});
it('points to production', () => {
const config = setupTest('realsite.com');
expect(config.API_ROOT).toEqual('https://api.realsite.com/api/v1');
});
it('points to staging', () => {
const config = setupTest('staging.realsite.com');
expect(config.API_ROOT).toEqual('https://staging.api.realsite.com/api/v1');
});
it('points to QA', () => {
const config = setupTest('qa5.company.com');
expect(config.API_ROOT).toEqual('https://api.qa5.company.com/api/v1');
});
it('points to dev (default)', () => {
const config = setupTest('localhost');
expect(config.API_ROOT).toEqual('http://localhost:8080/api/v1');
});
it('points to dev (custom)', () => {
process.env.REACT_APP_BACKEND_HOST = 'custom';
const config = setupTest('localhost');
expect(config.API_ROOT).toEqual('custom/api/v1');
});
function setupTest(hostname) {
setHostname(hostname);
return require('./api-config');
}
function setHostname(hostname) {
Object.defineProperty(window.location, 'hostname', {
writable: true,
value: hostname
});
}
Deploying React to Multiple Environments was originally published by Dave Ceddia at Dave Ceddia on July 24, 2017.
CodeProject