React Application: Build Once, Deploy Anywhere Solution
Published: 2023-06-23 18:06:52

---
title: React Application: Build Once, Deploy Anywhere Solution
published: true
description: 
tags: 
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-06-23 15:12 +0000
---

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:

```plaintext
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:

```javascript
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:

```shell
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:

```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