Create-react-app: Support for staging builds

Created on 28 Sep 2016  ·  58Comments  ·  Source: facebook/create-react-app

Hi, I thought about adding support for staging builds. I am not sure if this requires ejecting though, or could be a feature of CRA.

Our use case is that we create staging builds to deploy to our staging servers. This allows us to debug the app in a production like environment, but with the benefit of non-minimised code including proper sourcemap support and still includes the development warnings and notifications, as well as devtools support.

It can look very similar to webpack.config.prod.js, but with changes to L52-L54 and removal of webpack.optimize.UglifyJsPlugin.

proposal

Most helpful comment

This really should have been as simple as:

$ NODE_ENV=development npm build && s3 sync ./build/ s3://mybucket

You can't really mistakenly use the dev keys when you've explicitly set it on NODE_ENV. By forcing the env keys it's making it worse for everyone, because we can't easily support multiple environments.

You shouldn't try to save people from mistakes in there deployment process. A framework should provide the tools to support development, and a good one will help avoid pitfalls through documentation but not through pad locks :).

All 58 comments

This is something worth considering for the future although I think we won’t add this near term.

still includes the development warnings

What is your use case for development warnings in staging builds?

What is your use case for development warnings in staging builds?

Invariant warnings for instance, but also propType checks.

Invariant warnings for instance

Which ones are you referring to? “Invariants” are not warnings, those are hard errors and exist both in production and development builds.

Sorry, I meant more descriptive errors in the case of invariants. It serves the same purpose though, by making errors / bugs more transparent.

I just pushed a first idea of how it could work. It uses the production Webpack config as a base and adds the relevant changes on top of it. Other than that it's mostly adding the proper scripts and small modifications. If you consider supporting it, let me know so I can create a pull request.

An alternative option could be to add a minify flag (react-scripts build --minify=false). We would forego on React's warning etc., which are removed in the production build, but it would make errors more transparent / easier to debug in a staging environment.

Bump - a staging build script would be good, or possibly the ability to override the NODE_ENV?

YES @msmfsd ! We need this. Maybe make passible to set NODE_ENV as you like on the build.

Something like:
"NODE_ENV=WHATEVERYOUWANT npm run build"

What's the alternative right now when deploying? I mean how is it possible to set 'NODE_ENV=staging'

@firaskrichi create a .env file in the root folder of your application, with the variable you want. For eg.: REACT_APP_ENV=whatever

console.log(process.env.REACT_APP_ENV) // will return "whatever".

I meant when deploying. Seems that NODE_ENV is set to production when deploying.

@firaskrichi indeed.

The only way I could find to control my environments was through custom vars. Since I use Jenkins, I created scripts to build the .env file according to my environment needs.

Right know NODE_ENV is 'development' before building, and 'production' after. You can't edit it.

I just set custom vars on Heroku and it works. Thanks @angusyoung84

I can confirm that during build time (yarn run build) NODE_ENV is hard coded to 'production'. It doesn't help if you set NODE_ENV to something else via shell / .env file.

@gaearon wouldn't it make more sense if NODE_ENV found in shell / .env will have precedence and fallback to 'production' during build time ?

No, this is a dangerous footgun and will result in people unknowingly deploying development builds to production. Unfortunately I've seen this enough times to know it has to be hardcoded, and if we want to expose a separate build mode, it needs a separate command that you wouldn't confuse with a regular build.

Somewhat related: https://github.com/facebookincubator/create-react-app/issues/1070.

But to address your first points:

but with the benefit of non-minimised code including proper sourcemap support

We already include sourcemaps in production builds now.

as well as devtools support.

React DevTools have always been working with production builds.

I understand your reasoning.

I ended up interpolating index.html on the server, like the docs suggest. I tried to avoid this thing and be 100% client side logic, but it's not that bad.

However, I do think that this hard coded production when building behavior is not "standard" in Node. I expected NODE_ENV to be taken from an env variable and I was a little surprised that it was set to 'production' no matter what I did. Maybe the docs should mention this just to be sure. Maybe here: https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-build

By the way, I think the docs are really great! cover every common use case and explains everything really well. Thanks 👍

Happy to take a PR for docs.

Jumping into the fray on this one - @ehtb, have you had any luck putting together a staging PR? It looks like your fork has a fair bit of work done to it. My use case is pretty much the same as yours - I'd like my staging environment to mirror production, but to have traceable code (and use a staging data warehouse, which I'd switch to from the NODE_ENV setting).

@mbifulco Sorry for the late reply, my example repo has diverged too far to be of any use anymore and probably wasn't right way forward for CRA.

This really should have been as simple as:

$ NODE_ENV=development npm build && s3 sync ./build/ s3://mybucket

You can't really mistakenly use the dev keys when you've explicitly set it on NODE_ENV. By forcing the env keys it's making it worse for everyone, because we can't easily support multiple environments.

You shouldn't try to save people from mistakes in there deployment process. A framework should provide the tools to support development, and a good one will help avoid pitfalls through documentation but not through pad locks :).

Not @gaearon, but the method I use:
Set REACT_APP_API_DOMAIN as an environment variable. Can be anything obviously, but must be prefixed with REACT_APP. Use it in your codebase as process.env.REACT_APP_API_DOMAIN.

I use cross-env to set env vars when building locally. Can also use dotenv or similar. https://github.com/facebookincubator/create-react-app/issues/790#issuecomment-331507493 suggests create-react-app may just load .env automagically, I haven't used that method.

Ah, I see. You want to basically extend a base .env file and replace just the one variable. I'll defer comment then as I don't build staging/prod locally.

I've had the same issue in my app builds. I have multiple environments I need to deploy to (staging-1, qa, dev, prod, etc). In CRA, NODE_ENV seems to be used mostly for development vs an optimized build, but we need to be able to have optimized builds with different sets of environment variables.

My current hack is to circumvent .dotenv altogether, but it would be great to simply be able to specific something like APP_ENV=staging with NODE_ENV=production.

Time for another environment variable? :)

@jamesmfriedman I have created #3178, you can give it a try.

The trick here is that react/dotenv overwrite any env variable already set.

So I made a wrapper script build.js which I can run locally like this NODE_ENV=staging npm run build and the cool thing is that when appveyor builds production build which it, if build successfully uploads automatically to cloud servers it will also set some custom variables on build time.

const util = require("util");
const exec = util.promisify(require("child_process").exec);

process.env.NODE_ENV = process.env.NODE_ENV || process.argv[2] || "production";

// Load environment variables from .env files - the CRA way
require("react-scripts/config/env");

process.env.REACT_APP_VERSION = process.env.APPVEYOR_BUILD_VERSION || "local";
process.env.REACT_APP_COMMIT = process.env.APPVEYOR_REPO_COMMIT ||
  "local";

const BRANCH =  process.env.APPVEYOR_REPO_BRANCH;

// If building on appveyor - add and change some env vars.
if (BRANCH === "master" && process.env.NODE_ENV === "production") {
  process.env.GENERATE_SOURCEMAP = false;
}

async function main() {
  await exec("npm run build", { env: process.env });
}

try {
  process.stdout.write(`Building ${process.env.NODE_ENV}... `);
  main();
  process.stdout.write(`✅ \r\n`);
} catch (e) {
  console.log(`❌ Building of ${process.env.NODE_ENV} failed. `);
  console.log(e);
  process.exit(1);
}

I ended up doing this for my CI/CD using shippable:

In my case, all I'm using NODE_ENV for in my production code is to determine which set of APIs react needs to send requests to.

import defaultConfig from './config';
import routesBuilder from './routes';

// Get environment name
const ENV = process.env.REACT_APP_NODE_ENV || 'development';

const config = Object.assign({}, defaultConfig.default, defaultConfig[ENV]);
const routes = routesBuilder(config);
export default config;
export {routes};

This is how config.js looks like:

export default {
  development: {
    mainApiUrl: 'https://api-dev.mydomain.com/',
    secondaryApiUrl: 'https://api2-dev.mydomain.com/',
  },
  production: {
    mainApiUrl: 'https://api.mydomain.com/,
    secondaryApiUrl: 'https://api2.mydomain.com/,
  }
};

And this is how my deployment script looks like (much less relevant specific to shippable):

if [ "$BRANCH" == "master" ]; then export REACT_APP_NODE_ENV="production"; else export REACT_APP_NODE_ENV="development"; fi

This worked great for my use case.

Maybe it's worth adding to the docs as it looks like it's a common scenario.

@mjsisley's solution is way under appreciated.

This whole concept of preventing NODE_ENV to be overriden to prevent people from pushing their AWS keys to prod or pushing a dev build to prod feels like trying to fool-proof a tool that intrinsically requires you to have a certain level of knowledge; Enough, in my opinion, to know how NOT to push those keys to prod. I don't see a reason why would this tool want to fool-proof that.

That being said, what will probably happen is that instead of using NODE_ENV they will just create a REACT_APP_NODE_ENV custom env variable, and push their AWS keys/dev build to prod accidentally anyway 🙄. I don't see the benefit. It's just forcing us to create a non-standard environment variable to do environment checks.

I don't think that is a problem that this tool should be focusing to prevent

Edit: fixed grammar.

CRA2 defaults should be safe for general use, but denying overrides because it "is a dangerous footgun" is not a valid excuse. The fact that CRA2 explicitly says in the console, "Creating an optimized production build..." should be enough if the developer "accidentally" typed in NODE_ENV=development...

+1 for some way to override this. Don't care if it's DANGEROUSLY_SET_NODE_ENV=Staging :D

I could use this for css as I'm currently trying to diagnose a styling issue that happens with yarn build but not with yarn start.

Here's the solution to this: https://facebook.github.io/create-react-app/docs/deployment#customizing-environment-variables-for-arbitrary-build-environments

Everything works when I run in yarn watch
But on build
image
which is https://reactjs.org/docs/error-decoder.html/?invariant=130&args[]=undefined&args[]=
Well, no clue. Lets try see source code
image
Pretty much useless. How to fix it? With magic 🔮

Chiming in here. I'm running my development API server in minikube; therefore it is going to have a different IP address / hostname than the application when started with npm start. I could use the "proxy" value in packages.json as documented, if it worked. Since it does not work for me, I ended up researching how to actually get CORS to work, which was a big enough pain that I wrote this. I could have avoided days of work, which is weeks of real time given that my development projects are not my day job, had there been an ability to publish a development build to a local directory. With a local persisted development build, I would have exposed it from the same host via another minikube container proxied to the same host. Why work this way? Because my basic principle that I'd prefer my development environment to be as close as reasonably possible to my production environment, and my production environment is going to be pure Kubernetes.

I know I could eject and tweak things. I know I could choose a different strategy to run my development API server. I like the simplicity that we get with create-react-app, I vehemently disagree with the design decision that there is no easy way to persist a development build. That design decision significantly complicates the use of create-react-app in my use case. Granted, my use case may not be your target for create-react-app, which is more my problem than yours.

Having said that, there seems to me that there are other potential safeguards for the concerns raised earlier in the discussion. Some of which already exist in the project. If, for example, I edit the URL in the browser used to load the build served by npm start to an alias of localhost, the app doesn't run. I get an error that the origin doesn't match the HOST setting. Brainstorming here, if you had a package.json value for development build host name, couldn't you use that mechanism to avoid running a development build on any host other than the one the developer explicitly identified as valid for development builds?

Thanks for listening, thanks for a very useful project, and hopefully my whining isn't excessive. Back to my project now.

I created a .env.production.local on staging server which contains

NODE_PATH=src/
REACT_APP_API_ROOT=http://api.dev.foo.bar

as this file would be ignored and has higher priority than .env.production

hope it helps.

I solved this by using npm's env-path and then in my package.json file:

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "build:staging": "env-path -p .env.staging react-scripts build"
...
}

( 'start' by default uses .env.development and
build to .env.production)
.env.staging file:

REACT_APP_VAR=**staging-var***

and the just npm run build:staging....

@Liel208 I cant seem to get that working env-path: command not found happens every time?

@The-Code-Monkey make sure you install this package: https://www.npmjs.com/package/env-path, then @Liel208's solution should work.

So there's still no way to run some "staging" environment now where there are watching js files disabled and hot-reloading disabled? It eats a lot of memory on our staging server (which is supposed to be a testing server).

I run my development server inside Docker and I cannot find a way to get it to use the .env.development settings.

I recently ran into this issue. I created a fork that adds --development and --production flags to the start and build commands. Now I can run react-scripts build --development (and react-scripts start --production, for that matter).

Is this something that CRA would be interested in merging?

https://github.com/Terrabits/create-react-app/commit/ec2bd4a3de7d320419a645a101aefef049c88376

... I don't have much experience with open source contribution or pull requests, so if there is value in this feature can someone help me make sure my code meets the standards of the project and walk me through the pull request?

This issue is well-aged like a fine European wine 🍾
We are using mobx and it has these nice checks for production environment https://github.com/mobxjs/mobx/blob/master/src/core/spy.ts which disable mobx dev tools on staging environments ☹️
Please consider adding staging environments support 🙏

I think this issue already has some solutions:

  1. https://create-react-app.dev/docs/production-build#profiling
    "scripts": {
        "build:prod": "react-scripts build",
        "build:staging": "react-scripts build --profile",
    },
  1. https://create-react-app.dev/docs/deployment/#customizing-environment-variables-for-arbitrary-build-environments
    "scripts": {
        "build:prod": "react-scripts build",
        "build:staging": "env-cmd -f .env.staging react-scripts build --profile",
    },

@someden These solutions address only some special cases, but not the whole issue.
For example, they do not solve mine.

@someden

  1. Profiling is part of but not wholly sufficient for development builds
  2. react-scripts ignores/overwrites certain environment variables to ensure that start is always in a development environment (e.g. NODE_ENV=development) and build is always in production (e.g. NODE_ENV=production). The solutions that involve custom environment variables do not resolve this.

The only solutions I've found that build in development mode involve either ejecting CRA or building your own react-scripts, both of which are pretty painful options.

I got stuck on @Liel208's workaround for 30 minutes because I decided my custom variable would be named BUILD_ENV. This somehow gets plucked from the process.env if used. So anyone thinking of using the same variable name; don't. 😁

Edit

Okay, so actually tried APP_ENV and LADIDAH_ENV which also get pulled from the process.env object. So I'm guessing REACT_APP_VAR specifically gets passed on by create-react-app?

@Terrabits go ahead and open a PR please :)

just read here https://github.com/facebook/create-react-app/blob/master/CONTRIBUTING.md#submitting-a-pull-request

NOBODY from the team has worked on this, and regarding the testing you could just explain a little bit the changes you introduced, on the PR description...

As per the code standards, in my opinion I would say that it's good... however, the process is to open the PR, and then comments can be made, and you can work from there. You can reference this issue on the PR, along with a descriptive title, you can also put fixes #790

But yeah, for now, i'm using your fork, so I would like this one to be added!

Strange to me that this was dismissed by the core team, as it seems like such an obvious need. I have two versions of an API key in my .env and .env.production files, for dev and prod which is fine. When I build and deploy to my staging environment, I explicitly don't want to use my production API key. I would prefer a staging-specific key, like how every environment everywhere generally works. I find it baffling that I can't seem to do this?

I guess I can pass my server ENV down to the client and jury-rig it, but why?

@bmoeskau there is no problems with your case, it is simple to do it, just read my comment above https://github.com/facebook/create-react-app/issues/790#issuecomment-568470713

Being able to set development mode in a packaged build is a very useful thing. At this point all of our analytics are going to our production bucket because we have to generate builds to push to our staging environments. This is unacceptable from a user analytics standpoint. We can't have internal testing in our staging environments mucking with the data from our prod environment. I'm really surprised this hasn't been accounted for by now.

I recently ran into this issue. I created a fork that adds --development and --production flags to the start and build commands. Now I can run react-scripts build --development (and react-scripts start --production, for that matter).

Is this something that CRA would be interested in merging?

Terrabits@ec2bd4a

@Terrabits Just a quick note, I skimmed through your diff and it seems to me you're bypassing PUBLIC_URL handling, which we rely on for our pre-staging area, where a single webserver serves several React webapps each one under its own subpath.

If you just want to inspect the output if it was development, you can temporarily fix this by editing node_modules/react-scripts/scripts/build.js and replacing production with development:

process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

const config = configFactory('development');

Has there been any traction on this? I'm trying to output a build with sourcemaps, etc. to be served with an app server. Maybe there's another way to do this that I'm not seeing, but right now it looks like if I want sourcemaps, I have to run npm run start, which serves from localhost:3000, and then bind my app server to a different port, and then enable CORS in the app server, which I can't do.

@gaearon also for your consideration: I think it's noble to want to protect users from accidentally releasing a development build, but there's other ways to protect against that like CI. Even if a development build was released to production, that code has to work. If it doesn't, it means it hasn't been tested, and at that point there's more issues in the process than just pushing a huge website to prod right? I don't expect this to necessarily change your mind, it's just a different perspective.

4 years for a flag? hmmm. this is related to the same issue of not being able to output a watchable dev build. preventing from using CRA in extension development.

If you just want to inspect the output if it was development, you can temporarily fix this by editing node_modules/react-scripts/scripts/build.js and replacing production with development:

process.env.BABEL_ENV = 'development';
process.env.NODE_ENV = 'development';

const config = configFactory('development');

And you can eject it before if you want to see the script build.js in the scripts folder (rather than diving into the node_modules).
Then you can make this change line 4-5 and 47.

You may have also to change the default appBuild: resolveApp('dist'), in the config/path.js file since development build files inside a dist folder.

Has there been any traction on this? I'm trying to output a build with sourcemaps, etc. to be served with an app server.

This is exactly my use case. There's quite a high barrier to doing something so common.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

xgqfrms-GitHub picture xgqfrms-GitHub  ·  3Comments

rdamian3 picture rdamian3  ·  3Comments

dualcnhq picture dualcnhq  ·  3Comments

adrice727 picture adrice727  ·  3Comments

Aranir picture Aranir  ·  3Comments