DEV Community

Cover image for React Runtime Environment Variables
AK DevCraft
AK DevCraft Subscriber

Posted on • Edited on

React Runtime Environment Variables

Introduction

As single page application (SPA) that runs as static page inside a browser runtime environment, and inside a browser, there is nothing like a runtime variables that could be used by SPA. However, there are few hacks through which we can implement dynamic environment variable in a React single page application.

But the question is why we need a runtime variable for static page? Well, for what I have experienced there are a few cases which compel us to look for runtime or dynamic variable in a React app or for that matter of fact for any SPA. For example, different API endpoints for local, pre-prod, and production, different API keys for pre-prod and prod, and likewise.

Getting Started with local development

We need a few configurations to get started for local development:

  1. An environment file
  2. A Bash script
  3. A NPM script
  4. Include script tag in public/index.html
  5. Script where runtime variable is needed

1. An environment file

Create an env.preprod file, create this file at /public/env/ location e.g. /public/env/env.preprod
This is the file where the runtime environment variable will be stored, there could more than one variable in the file. Why under public directory? as it will be bundled during the build process into tarball

//Filename: env.preprod
REACT_APP_RUNTIME_PREPROD_KEY=xyz
Enter fullscreen mode Exit fullscreen mode

2. A Bash script

Bash script that will be executed during npm start for local, that will create the env-config.js with content from env.preprod file and same for pre-prod during deployment. For prod, we'll have the default env-config.js file.

Filename: env.sh

#!/bin/bash

# look for runtime env file
if [ ! -z "${2}" ]; then
  envFile="${1}"/env."${2}"
fi

#If can't find it then exit
if [[ ! -f "$envFile" ]]; then
echo "Env file doesn't exist!"
exit 1;
fi


# create runtime env JS file
if [[ ! -z "${1}" ]]; then
  envJs="${1}/env-config.js"
fi

#Recreate config file
rm -rf ${envJs}
touch ${envJs}

# Add assignment 
echo "window._env_ = {" >> ${envJs}

# Read each line in .env file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
  # Split env variables by character `=`
  if printf '%s\n' "$line" | grep -q -e '='; then
    varname=$(printf '%s\n' "$line" | sed -e 's/=.*//')
    varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//')
  fi

  # Read value of current variable if exists as Environment variable
  value=$(printf '%s\n' "${!varname}")
  # Otherwise use value from .env file
  [[ -z $value ]] && value=${varvalue}

  # Append configuration property to JS file
  echo "  $varname: \"$value\"," >> ${envJs}
done < ${envFile}

echo "};" >> "${envJs}"

echo "generated ${envJs} with content"
cat ${envJs}
Enter fullscreen mode Exit fullscreen mode

3. A NPM script

This will wire up as the prestart npm script and execute the bash script.

//Change in package.json file
"prestart" : "chmod +x ./public/env/env.sh && ./public/env/env.sh ./public/env preprod"
Enter fullscreen mode Exit fullscreen mode

4. Include script tag in the public/index.html

The env-config.js created so far needs to be loaded in the index.html, else we can't use it. When env-config.js is created, the browser's window object is assigned a runtime variable.

<!--Change in index.html-->
<head>
  <script src="%PUBLIC_URL%/env/env-config.js?d=20210529"></script>
</head>
Enter fullscreen mode Exit fullscreen mode

5. Script where runtime variable is actually used

And now for all the hard work done so far, it's time to ripe/use the runtime variable. Since the variable is assigned as a window object, now we can use the way we want. It can be used in the vanilla JS file or in a React app JSX/TSX file. Checkout Sample Code

//Filename: some-important.js
const RUNTIME_ENV_KEY = window?._env_?.REACT_APP_RUNTIME_PROD_KEY ? window._env_.REACT_APP_RUNTIME_PROD_KEY : window?._env_?.REACT_APP_RUNTIME_PREPROD_KEY;

Enter fullscreen mode Exit fullscreen mode

Also, include some-important.js in the index.html head tag:

<!--Change in index.html-->
<head>
  <script src="%PUBLIC_URL%/some-important.js?d=20210529"></script>
</head>
Enter fullscreen mode Exit fullscreen mode

For Preprod

  1. Execute script during deployment
  2. location.conf (when using NGINX)

1. Execute script during deployment

Include a script to execute the env.sh in the deployment process. For docker image details, checkout the reference section at the end.

Filename: preprod-deployment.sh

bash ./public/env/env.sh ./public/env preprod
Enter fullscreen mode Exit fullscreen mode

2. location.conf (when using NGINX)

When the Nginx server is used as a web server, allow access to env-config.js file.

Filename: location.conf

location ~ /env/(.+\.(?:js))$ {
  expires -1;
  add_header Cache-Control "public"
}
Enter fullscreen mode Exit fullscreen mode

For Production

1. Create default env-config.js

Creating a default reduces the effort to configure any steps needed during the production deployment. But if we want, we can create another env file like env.prod and run the same during production deployment. However, this is totally up to you!

Filename: env-config.js

window._env_ = {
  REACT_APP_RUNTIME_PROD_KEY=runtime-env-value
};
Enter fullscreen mode Exit fullscreen mode

Sample Code

The code snippet presented in this blog is available in Github, Sample Code. In case you're just interested in the files, checkout GitHub Gist

If you have reached here, then I did a satisfactory effort to keep you reading. Please be kind to leave any comments or ask for any corrections. Happy Coding!

Reference:

My other blogs:

Top comments (6)

Collapse
 
talish_george_99355f81dd5 profile image
Talish George

Please find the Powershell version

Filename: env.ps1

Look for runtime env file

if ($args.Count -gt 0) {
$envFile = "$($args[0])\env.$($args[1])"
}

If can't find it then exit

if (-not (Test-Path -Path $envFile -PathType Leaf)) {
Write-Host "Env file doesn't exist!"
exit 1
}

Create runtime env JS file

if ($args.Count -gt 0) {
$envJs = "$($args[0])\env-config.js"
}

Recreate config file

Remove-Item -Path $envJs -Force -ErrorAction SilentlyContinue
New-Item -Path $envJs -ItemType File

Add assignment

Add-Content -Path $envJs -Value "window.env = {"

Read each line in .env file

Each line represents key=value pairs

$envFileContent = Get-Content -Path $envFile
foreach ($line in $envFileContent) {
# Split env variables by character =
if ($line -match "=") {
$varname, $varvalue = $line -split "=", 2
}

# Read value of the current variable if it exists as an Environment variable
$value = [System.Environment]::GetEnvironmentVariable($varname, [System.EnvironmentVariableTarget]::Process)
# Otherwise, use value from .env file
if ([string]::IsNullOrEmpty($value)) {
$value = $varvalue
}

# Append configuration property to JS file
Add-Content -Path $envJs -Value " ${varname}: "$($value)","
}

Add-Content -Path $envJs -Value "};"

Write-Host "Generated $envJs with content"
Get-Content -Path $envJs

Collapse
 
akdevcraft profile image
AK DevCraft • Edited

@talish_george_99355f81dd5 Thanks for adding powershell version, however, I was thinking would it make more sense to be its own blog? as not many will reach to the comment section. If you can create a brief blog, I can add that link in mine. Any thoughts?

Collapse
 
talish_george_99355f81dd5 profile image
Talish George

"prestart": "powershell -ExecutionPolicy Bypass -File .\public\env\env.ps1 .\public\env prod",

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
sperezm97 profile image
Sebastian Perez • Edited

Is this secure? i mean if we inspect the web page and typed windows.env in the console of the browser, it will print all the env variables there, what do you think ?

Collapse
 
akdevcraft profile image
AK DevCraft

You’re correct it’s not secure, however, is anything secure with JS? I would say probably not as JS file can viewed in the browser. That’s why it’s not recommended to keep sensitive information in the frontend like API Key, JWT Token etc. I hope this answered your question