Dynamic Base URLs in Axios for Staging and Production

Managing different API environments is a common requirement in modern web development. This article demonstrates how to dynamically toggle between staging and production base URLs in the Axios HTTP client using build-time environment variables, custom Axios instances, and runtime dynamic switching.


Method 1: Environment Variables (Build-Time Switching)

The most standard approach is to use environment variables provided by your bundler or runtime (such as Node.js, Vite, or Next.js).

Create your environment files:

.env.staging

VITE_API_BASE_URL=https://staging-api.example.com/v1

.env.production

VITE_API_BASE_URL=https://api.example.com/v1

Then, initialize Axios using the appropriate environment variable:

import axios from 'axios';

// Vite example (use process.env for Node/Webpack/CRA)
const baseURL = import.meta.env.VITE_API_BASE_URL || 'https://staging-api.example.com/v1';

const apiClient = axios.create({
  baseURL,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});

export default apiClient;

Method 2: Hostname-Based Dynamic Switching

If you deploy a single build artifact across multiple environments, you can determine the base URL at runtime by inspecting window.location.hostname.

import axios from 'axios';

const getBaseURL = () => {
  const hostname = window.location.hostname;

  if (hostname === 'app.example.com') {
    return 'https://api.example.com/v1'; // Production
  }

  if (hostname === 'staging.example.com') {
    return 'https://staging-api.example.com/v1'; // Staging
  }

  return 'http://localhost:3000/v1'; // Local Development
};

const apiClient = axios.create({
  baseURL: getBaseURL(),
  timeout: 10000,
});

export default apiClient;

Method 3: Runtime Switching via Axios Interceptors

To allow users, testers, or administrators to switch environments directly within the application UI (e.g., via a settings toggle or local storage), update the URL dynamically using an Axios request interceptor.

import axios from 'axios';

const ENVIRONMENTS = {
  staging: 'https://staging-api.example.com/v1',
  production: 'https://api.example.com/v1',
};

const apiClient = axios.create({
  timeout: 10000,
});

// Interceptor dynamically sets the baseURL before every request
apiClient.interceptors.request.use((config) => {
  const selectedEnv = localStorage.getItem('app_env') || 'production';
  config.baseURL = ENVIRONMENTS[selectedEnv] || ENVIRONMENTS.production;
  return config;
});

// Helper function to change the environment
export const setEnvironment = (envName) => {
  if (ENVIRONMENTS[envName]) {
    localStorage.setItem('app_env', envName);
  }
};

export default apiClient;

Summary of Best Practices

  1. Use Build-Time Configurations if your deployment pipeline generates separate staging and production releases.
  2. Use Hostname Detection if you deploy identical Docker containers or static builds to different domains.
  3. Use Request Interceptors if you need in-app switching for QA testing without rebuilding or redeploying the application.