React Application: Build Once, Deploy Anywhere Solution
Eamonn Walsh
Posted on June 23, 2023
In this blog post, we will explore a solution for the build once deploy many issue faced by projects working with web application libraries such as React.
In React environment-specific configuration is baked in at build time, meaning you most likely need to create individual build artifacts for dev, test, uat, live etc. This can turn into a significant time sink if the build process is even remotely lengthy.
The goal is to decouple environment-specific configuration from the build process, allowing the same build artifact to be deployed to multiple environments with different configurations.
Prerequisites
- Basic knowledge of React and Vite
- Familiarity with Docker and Kubernetes concepts
Steps
1. Placeholder-based Configuration
To make the application deployable to different environments, we need to remove all hard-coded references to environment variables and replace them with placeholders. During the build process, Vite will generate a deployable asset containing these placeholders instead of the actual environment variable values.
2. Move Environment Variables to Kubernetes
Store your environment-specific configuration in Kubernetes, preferably using a ConfigMap. This allows you to set the configuration on a per-environment basis.
3. Create a Shared Environment File
Create a single environment file, e.g., .env.all
, that will be used for all builds. Set your variables to align with the placeholder names. For example:
VITE_VARIABLE_1=%VITE_VARIABLE_1%
VITE_VARIABLE_2=%VITE_VARIABLE_2%
VITE_VARIABLE_3=%VITE_VARIABLE_3%
4. Create a Configuration Replacement Script
In your React application codebase, create a JavaScript script that can be executed inside your Docker container. This script will replace the placeholders with the actual environment variable values at deploy time, pulling the values from the ConfigMap in Kubernetes. It's recommended to create this script in a folder outside the /src
directory, such as /static
, to avoid it being included in the JavaScript asset during build time.
5. Copy the Replacement Script
Use Vite's static copy plugin to move the replacement script into the build output folder (by default, dist
). Add the following configuration to your Vite project:
import { react } from '@vitejs/plugin-react';
import viteStaticCopy from 'vite-plugin-static-copy';
export default {
// Other Vite configuration options...
plugins: [
react(),
viteStaticCopy({
targets: [
{
src: './static/replacePlaceHolders.js',
dest: './static/replacePlaceHolders.js',
},
],
}),
],
};
6. Run the Build Command
Run your build command as usual, but make sure to point to your single .env.all
file using the mode
flag. For example:
tsc -p tsconfig.build.json && vite build --mode all
7. Verify the Build Output
Verify that the built asset contains the variable placeholders instead of the actual values. Also, ensure that the replacePlaceHolders.js
script exists in the dist
folder.
8. Update the Dockerfile
Update your Dockerfile to include Node.js as part of the image so that it can run the replacePlaceHolders.js
script during the image preparation phase.
The majority of Docker images used for deploying React applications do not typically necessitate the presence of Node. However, we specifically require Node to be running in order to access and retrieve environment-specific configuration set in your ConfigMap via the process.env object in Node.
Add the following command to the Dockerfile:
CMD ["node", "./replacePlaceHolders.js"]
This command will execute the JavaScript script in Node.js, providing access to the variables set in the ConfigMap. You can replace placeholders in files using the following code snippet:
const fs = require('fs');
const filePath = '<path-to-file>';
let fileContent = fs.readFileSync(filePath, 'utf-8');
let fileContent = await fs.readFile(<path-to-file>, 'utf-8');
fileContent = fileContent.replace('%VITE_VARIABLE_1%', process.env.VITE_VARIABLE_1);
Conclusion
There are a few other approaches to do this but they all need to add what I would consider as configuration logic to the app itself and don't run at deploy time. There is no perfect solution
Posted on June 23, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.