Have you ever needed to deploy a React application to multiple environments?
If you've ever had to do the above, then this scenario may sound familiar to you: You already have an existing build file, you wish to change some environment variables, then re-deploy it to a new environment.
Unfortunately, you soon realize that the environment variables have been hard-coded within the build file. This means that you cannot change the environment variables without re-building.
Are there any other ways to modify environment variables?
We asked around to see if the other projects had a better way to handle it. It turned out that different teams had their own approach:
- Some teams simply re-built it for each environment
- Some teams fetched the environment variables asynchronously from an external data source
- One team stored all the environment variables in the app, then selected the environment based on the URL
- One team stored a placeholder environment variable, then replaced it before serving the file
What would an ideal flow look like?
All of the above had certain tradeoffs that we weren't willing to make. We were looking to build something that fulfilled the following:
- Does not require a rebuild
- Minimal code change required
- Allows synchronous access of environment variables
- Easy to integrate with your current workflow
- Simple and straightforward
react-inject-env: a tool that allows you to modify environment variables after the static file has been built
The short and simple explanation is that it creates an env.js
file in the /public
folder. The file is then executed at the start and the variables are loaded into the window
object.
Here's how to use it
- Install react-inject-env
npm install react-inject-env --save-dev
yarn add react-inject-env --dev
2. Add the following to index.html
<script src='/env.js'></script>
3. Create a new file called env.js
and copy the following code:
export const env = { ...process.env, ...window['env'] }
4. Replace all instances of process.env
with the newly created env
variable
import { env } from './env'
export const App = () => {
return (
<div style={{backgroundColor: env.REACT_APP_COLOR}}>
<span>{env.REACT_APP_MAIN_TEXT}</span>
</div>
)
}
5. Build your static files using npm run build
/ react-scripts build
/ whatever your build script is.
6. Set your environment variables using the following command:
[env variables] npx react-inject-env set
# with a black background
REACT_APP_COLOR=black REACT_APP_MAIN_TEXT="Black Background" npx react-inject-env set
# with a blue background
REACT_APP_COLOR=blue REACT_APP_MAIN_TEXT="Blue Background" npx react-inject-env set
# for windows
set REACT_APP_COLOR=navy&& set REACT_APP_MAIN_TEXT=Navy Background&& npx react-inject-env set
7. Open build/env.js
to verify that your variables are present.
These variables will then be read by your app at runtime.
And that's it, you're done!
If you need to modify your environment variables again, you can either (a) repeat Step #6, or (b) modify build/env.js
directly.
What about <insert tool>?
This utility was built to be as simple as possible, so it should integrate and work well with most other tools.
.env / dotenv
react-inject-env
will automatically detect environment variables in your .env
file located in your root folder.
Note: Environment variables passed in through the command line will take precedence over .env
variables.
Typescript
In Step #2, create a file named env.ts
instead of env.js
declare global {
interface Window {
env: any
}
}
// change with your own variables
type EnvType = {
REACT_APP_COLOR: string,
REACT_APP_MAIN_TEXT: string,
REACT_APP_LINK_URL: string,
REACT_APP_LOGO_URL: string
}
export const env: EnvType = { ...process.env, ...window.env }
Docker
react-inject-env can also be neatly integrated with Docker
FROM node:16.10-slim
COPY . /app
WORKDIR /app
RUN npm install
RUN npm run build
EXPOSE 8080
ENTRYPOINT npx react-inject-env set && npx http-server build
docker build . -t react-inject-env-sample-v2
docker run -p 8080:8080 \
-e REACT_APP_COLOR=yellow \
-e REACT_APP_LOGO_URL=./logo512.png \
-e REACT_APP_MAIN_TEXT="docker text" \
-e REACT_APP_LINK_URL=https://docker.link \
react-inject-env-sample-v2
Summary
This package has greatly enhanced our workflow and has cut our build and deployment times by over 10 minutes. In the past, we had to build 4x times - once for each environment - but now we only need to build it 1x. Deployments are blazing fast now!
It was built with simplicity in mind, so regardless of which tool you are using, you should be able to integrate it as part of your workflow!
Links
For more detailed information and support and samples, you may check out the following links:
Top comments (19)
That npx command you run - npx react-inject-env set - with the environment variables before the command, e.g., REACT_APP_COLOR=black REACT_APP_TEXT="Black Background" npx react-inject-env set...
Is that a Bash command? Can this be reworked into a Windows cmd? I've tried a few ways but don't see how to do it.
Hello! Sorry for the late reply. I've updated the instructions to show how to use it with Windows!
Hello, where must i put this command bash at windows? is that inside folder code? or inside build output folder?
Hey! Have you tried the sample code yet? Does it work for you?
Alternatively you can upload a sample of your code on GitHub and I'll try to take a look at it!
instructions are not that clear.
if you want to add script file to the
index.html
that means it is located atpublic
folder which is outside ofsrc
directory and you can't import something that falls outside ofsrc
folder.Thanks for this, I've always used Webpack externals and have been tasked with finding an elegant solution to injecting variables at runtime, and this is better than creating an endpoint to return the variables or loading a Json file. I have a question though; my app was using cache busting in Webpack but the npx script creates a new env.js in this case. I've switched cache busting off for now but is there any way the script can pick up the cache busting file, in my case env.5df60b24c6fb4378557c.js?
Hey! I'm not familiar with cache-busting on webpack as I typically use CRA. Do you think you could provide a sample?
Nice! Thanks for the feedback!
Hi I have installed the below package
For my React application I need to give the application url after I generate build using npm run-script build
so to statisfiy this need I am creating REACT_APP_HOST_IP environment variable and passing it through below command as suggested
REACT_APP_HOST_IP=X.X.X.X npx react-inject-env set
output
** Setting the following environment variables:
{ REACT_APP_HOST_IP: 'X.X.X.X' }
**
Below is the output of my env.js file present inside build folder.
_window.env = {
"REACT_APP_HOST_IP": "X.X.X.X"
}
_
error I am getting on browser console is below
GET http://{application-url}:8084/env.js net::ERR_ABORTED 404 (NOT FOUND)
console.log output is undefined of the script where I am using the value of REACT_APP_HOST_IP
So, in my application where I suppose to use this environment variable value it is showing me _undefined _ on console.log
thank you for creating this package but would please help me to solve the issue described above?
Have you managed to fix that? I have the same problem now...
Hey, First of all great post thanks for your insight and sharing of your knowledge with all of us. I was wondering, isn't this a security risk to be able to change or have all the env variables on the client browser in env.js? Some can change things and break application and might extract unwanted keys and leaks using the application?
Fundamentally, there's no difference in security. Whether you store the environment variables in
env.js
or whether you bundle it together with your.js
code, the variables are still stored on the client side and can still be mined or modified by the user. Secrets should not be stored on the client to begin with.Storing it in this manner does make it a lot easier for someone to mine or change the data though! If this is a concern then you could employ other techniques such as obfuscation.
What are the security implications when you set the dot env in an external script, are we risking any exposure to outside actors.
Good question! The biggest risk is that the current instructions provided does not lock it to any specific version. This means that if my npm account was compromised by a malicious attacker, they could add in additional line of code that sends all the data to a remote server. They could then publish a new version on npm, and everyone will execute the new malicious code instead.
To guard yourself against this, you can lock the version by installing and specifying it in your
package.json
. This will ensure that you will always run the same version of the script, where the source code can be verified at GitHub. This is good practice in general anyway, so I'll be editing the article to add instructions on how to do this.On a side note, React webpages are typically public and client-side, which means that they are typically not able to guard secrets. In general, they should not have any sensitive information being passed to them at build time.
Hello. Very helpful post, thank you.
Could you please provide solution, how to use env variables for development mode with that approach?
I tried this solution for development
It is works for development mode, but I receive this kind of error
"Uncaught SyntaxError: Unexpected token '<'"
dev-to-uploads.s3.amazonaws.com/up...
dev-to-uploads.s3.amazonaws.com/up...
declare global {
interface Window {
env: any
}
}
type EnvType = {
REACT_APP_HOST_IP_ADDRESS: string,
}
const DEFAULT_DEVELOPMENt_ENVS = {
...process.env,
REACT_APP_HOST_IP_ADDRESS: "localhost:7777"
}
const generateEnv = (): EnvType => {
if (process.env.NODE_ENV !== "production") {
return DEFAULT_DEVELOPMENt_ENVS;
}
return { ...process.env, ...window.env }
}
export const env = generateEnv();
I have the same problem. Did you solve it?
Facing the same issue. Any solutions?