One of the most common challenges in front-end development is managing environment-specific configurations. Unlike backend applications, where you can leverage environment variables easily, frontend applications run in a browser that doesn’t natively support environment variables. So often developers specify these configurations at build time and build the frontend app separately for each environment.
Take the example of a web application that has to be deployed across development, staging, and production environments.
Each environment requires different configurations, such as API endpoints and feature flags. To manage these, the development team configures these settings at build time.
While this approach initially seems straightforward, it introduces a host of issues.
Firstly, creating separate builds for each environment is time-consuming and inefficient. Every deployment requires generating a new build, duplicating efforts, and increasing the complexity of the deployment pipeline. Doing this for small bugs or enhancements is just a mere waste of time.
Secondly, managing multiple builds heightens the risk of human error. A single mistake, such as deploying a staging build to production, can lead to outages, customers losing trust, or security vulnerabilities.
These risks can have serious consequences, especially in a production environment where reliability and stability are paramount.
What can you do to avoid this?
Instead of embedding environment variables into your frontend application at build time, you can configure your application to load these variables dynamically at runtime. This will make your deployment process more efficient and less error-prone.
Here’s how to implement dynamic configuration at runtime:
1. Use Relative Paths
Ensure your frontend application can interact with your backend API using relative paths. This means the frontend does not need to know the full URL of the backend API; instead, it can rely on relative URLs determined by the server configuration.
Example: If your frontend is served from https://www.example.com and your backend API is at https://api.example.com, you can configure your frontend to make requests to relative paths like /api/endpoint instead of hardcoding the full API URL. This way, the frontend automatically adjusts to the correct backend URL based on the current environment.
2. Backend API for Configuration
Set up an endpoint in your backend that provides the necessary environment variables to the frontend. This endpoint can dynamically serve the configuration settings required by the frontend application.
Example: Create an endpoint like /api/config on your backend. When the frontend application starts, it can make a request to this endpoint to fetch its configuration settings. This endpoint would return a JSON object containing the necessary environment variables.
Steps of Implementation
1. Set Up Backend Endpoint
First, ensure your backend provides an endpoint (/api/config) that returns the required environment variables as a JSON object.
Example in Node.js/Express
const express = require('express');
const app = express();
const port = 3000;
app.get('/api/config', (req, res) => {
const config = {};
Object.keys(process.env).forEach(key => {
if (key.startsWith('FE_')) {
config[key] = process.env[key];
}
});
res.json(config);
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
2. Create loadConfig Function in Frontend
In your frontend application, create a config.js file with the loadConfig function:
javascript
async function loadConfig() {
const response = await fetch('/api/config');
const config = await response.json();
window.appConfig = config;
}
export default loadConfig;
3. Modify Application Entry Point
In your application's entry point (usually src/index.js or src/main.js), modify the code to call loadConfig before initializing the application:
Example in src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import loadConfig from './config';
loadConfig().then(() => {
ReactDOM.render(<App />, document.getElementById('root'));
}).catch(error => {
console.error('Failed to load configuration:', error);
// Handle the error (e.g., show an error message to the user)
});
4. Access Configuration in the Application
Within your application, access the configuration stored in window.appConfig as needed. For example, you might pass these configurations as props to your components or store them in a global state.
Example in a Component:
import React, { useEffect, useState } from 'react';
const MyComponent = () => {
const [config, setConfig] = useState(null);
useEffect(() => {
setConfig(window.appConfig);
}, []);
if (!config) {
return <div>Loading...</div>;
}
return (
<div>
<h1>API Endpoint: {config.FE_API_ENDPOINT}</h1>
{/* Use other config variables as needed */}
</div>
);
};
export default MyComponent;
By implementing dynamic configuration at runtime, you can streamline your deployment pipeline, reduce the risk of errors, and make your frontend application more flexible and resilient to changes in environment-specific settings.
This post was originally published on my Substack
Top comments (0)