DEV Community

Sébastien Belzile
Sébastien Belzile

Posted on • Updated on

Stop using Dotenv in your front-end

Dotenv is wonderful. It allows you to use environment variables in your code, hence separating the code from its running environment.

The problem I have with it is that it is common to see people using it in the front-end of their web application (served static files, non-SSR applications).

What is wrong with this?

Your environment becomes hard-coded in your files at build time. If you proceed this way, you need to build your application every time you wish to deploy it.

Alternative

Load your configuration at runtime, either from a backend or from a configuration file (with an hash in the name). This way, your CI and your CD become 2 independent components. You build only once as opposed to once per deployment. Furthermore, the same artifact is being deployed everywhere, saving you a couple build minutes, and increasing your confidence in what you are deploying.

Implementation example (configuration file)

In your index.html file, add a setting file that contains your environment variables:

<script src="./settings/settings.js"></script>
// must be placed before your js files
Enter fullscreen mode Exit fullscreen mode

File content example:

var environment = {
  "backendBaseUrl": "http://localhost:8000",
}
Enter fullscreen mode Exit fullscreen mode

Using Typescript? Type it:

type Environment = {
  backendBaseUrl: string
}

declare global {
  const environment: Environment
}
Enter fullscreen mode Exit fullscreen mode

Then use it:

const url = `${environment.backendBaseUrl}/potato`
Enter fullscreen mode Exit fullscreen mode

Your deployment pipeline simply needs to ensure this file is being generated/deployed, which is way faster than building your whole codebase.

Top comments (4)

Collapse
 
tuliocalil profile image
Tulio Calil

I don't know if this makes sense.
Normally things that need to be in the dotenv file are configs that will not change frequently (like backend base url).
Move these configs to a js file will let this more vulnerable to XSS attacks.
And about restart the application, I will need to "restart" the application anyway, cause I will need to build the new modifications and deploy it.
If you want to change some configurations and not dispatch a new build, these configurations cant be in the frontend base code.
Following your suggestion, I will need to have two pipelines, one for deploy the config file and don't dispatch the build and the other to deploy the entire application ?.
Maybe put these configs in a remote config or put this in a backend/serverless function is a better idea, no ?

Collapse
 
sbelzile profile image
Sébastien Belzile

Normally things that need to be in the dotenv file are configs that will not change frequently (like backend base url).

Totally agree.

Move these configs to a js file will let this more vulnerable to XSS attacks.

In the end, the job to do to perform an XSS attack is the same whether you use dotenv to compile your configuration to your JS or whether you serve a different JS file for these configurations. You are more vulnerable because there is a well structured config file instead of constants in a big bundled JS file.

And about restart the application, I will need to "restart" the application anyway, cause I will need to build the new modifications and deploy it.

I agree. There is no advantage related to this in what I am suggesting.

If you want to change some configurations and not dispatch a new build, these configurations cant be in the frontend base code.

I still agreed. Application configuration belongs to your deployment pipeline. Not to your front-end base code.

Following your suggestion, I will need to have two pipelines, one for deploy the config file and don't dispatch the build and the other to deploy the entire application ?.

No. My point is that application configuration should not be compiled into your main application bundle. An efficient pipeline has a build step that creates a reusable artifact (your compiled JS) that can be deployed / used in any deployment environment (dev, staging, e2e testing, prod, etc).

My point is: don't recompile your code once for every environment you need to deploy in. Have a basis that never changes (for a given commit), use configuration to target environment specificities.

In this setup, we do not need 2 pipelines. We need one job that deploys both at once (the files compiled by the build pipeline and the configuration file).

Maybe put these configs in a remote config or put this in a backend/serverless function is a better idea, no ?

What you are suggesting is totally a valid alternative to the file approach I am suggesting. It's a bit more work, but it is a valid approach. You would still need a "backend based URL" that differentiates between dev, staging and your other environments, but that would work fine.

Collapse
 
sirseanofloxley profile image
Sean Allin Newell

How would you deploy just one file separately from the SPA that the SPA imports in a typical CI/CD pipeline? Would love to see a sample repo with either CircleCI/GithubActions/Netlify set up with a bundler like webpack/parcel/esbuild to demo this decoupling.

Collapse
 
sbelzile profile image
Sébastien Belzile

Usually, in these cases, your bundler will output its results to a specific folder, ex: dist, www, out, etc.

The minimum you need is a command that takes a file from one place and copies it there:

cp config/prod.json dist/settings/environment.json
Enter fullscreen mode Exit fullscreen mode

Then, you can deploy the resulting folder.

Usually, in a CI/CD environment, you will want to set the content of these files to some CI/CD environment variables. In these cases, you may use something like envsubst or its node equivalent envsub.

Example of this (docker environment, files served by nginx)

entrypoint.sh:

#!/bin/sh
set -e

envsubst < /var/www/html/settings/settings.template.js > /var/www/html/settings/settings.js

exec nginx -g "daemon off;"
Enter fullscreen mode Exit fullscreen mode

Dockerfile:

[...]
COPY client/public/settings/settings.template.js usr/share/nginx/html/settings/settings.template.js
COPY client/config/entrypoint.sh /opt/entrypoint.sh
[...]
ENTRYPOINT ["sh", "/opt/entrypoint.sh"]
Enter fullscreen mode Exit fullscreen mode