Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / React

Deploying React to Multiple Environments

0.00/5 (No votes)
24 Jul 2017CPOL2 min read 7K  
How to deploy React to multiple environments

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.

Configure API Endpoints Dynamically

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:

JavaScript
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:

JavaScript
import { API_ROOT } from './api-config';

function getUsers() {
  return fetch(`${API_ROOT}/users`)
    .then(res => res.json)
    .then(json => json.data.users);
}

Configure Endpoints at Build Time

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.)

Configure Feature Flags at Build Time

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:

JavaScript
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.

JavaScript
// (this could also go in afterEach())
beforeEach(() => {
  // Clear the Jest module cache
  jest.resetModules();

  // Clean up the environment
  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');
}

// Set the global "hostname" property
// A simple "window.location.hostname = ..." won't work
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)