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
File content example:
var environment = {
"backendBaseUrl": "http://localhost:8000",
}
Using Typescript? Type it:
type Environment = {
backendBaseUrl: string
}
declare global {
const environment: Environment
}
Then use it:
const url = `${environment.backendBaseUrl}/potato`
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)
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 ?
Totally agree.
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.
I agree. There is no advantage related to this in what I am suggesting.
I still agreed. Application configuration belongs to your deployment pipeline. Not to your front-end base code.
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).
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.
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.
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:
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:
Dockerfile: