How I Built and Deployed Micro Frontends Using Webpack, Module Federation, Docker, and NGINX

WEBPACK DOCKER React JS

A step-by-step guide on how I implemented micro frontends with Webpack Module Federation, Docker, and NGINX for scalable React applications.


NGNishan Giri
2025-05-17
Best 7 React UI Libraries for 2024

Micro frontends allow large teams to build and deploy independently scalable frontend applications. In this blog, I’ll walk you through how I built a micro frontend architecture using Webpack Module Federation, Docker, and NGINX, based on a real-world project.


What Is Micro Frontend Architecture?

Micro frontends break a monolithic frontend into smaller, independently deployable pieces. Each team can own and deploy their part of the frontend.

Example structure:


Structuring the Repositories

- mfe-host/
- mfe1/
- mfe2/

Each MFE is an isolated React app with its own Webpack config and deployment pipeline.


🔧 Basic Webpack Setup

To better manage environment-specific configurations, we use webpack-merge to separate common, development, and production settings for both the Host and MFE1 apps.

First, install the dependency:

npm i webpack-merge

We’ll define three config files per app:

Host MFE Configuration

webpack.common.js

const { ModuleFederationPlugin } = require("webpack").container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
 
module.exports = {
  entry: "./src/index.js",
  resolve: { extensions: [".js", ".jsx"] },
  plugins: [
    new ModuleFederationPlugin({
      name: "mfe_host",
      remotes: {
        mfe1: "mfe1@http://localhost:3001/remoteEntry.js",
        mfe2: "mfe2@http://localhost:3002/remoteEntry.js",
      },
      shared: ["react", "react-dom"],
    }),
    new HtmlWebpackPlugin({ template: "./public/index.html" }),
  ],
};

webpack.prod.js

const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
 
module.exports = merge(common, {
  mode: "production",
  plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
    splitChunks: { chunks: "all" },
  },
});

webpack.dev.js

const { merge } = require("webpack-merge");
const common = require("./webpack.common");
 
module.exports = merge(common, {
  mode: "development",
  devServer: {
    port: 3000,
    historyApiFallback: true,
    hot: true,
  },
  devtool: "eval-source-map",
});

Remote MFE1 Configuration

webpack.common.js

const { ModuleFederationPlugin } = require("webpack").container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
 
module.exports = {
  entry: "./src/index.js",
  resolve: { extensions: [".js", ".jsx"] },
  plugins: [
    new ModuleFederationPlugin({
      name: "mfe1",
      filename: "remoteEntry.js",
      exposes: {
        "./Page": "./src/pages/Page",
      },
      shared: ["react", "react-dom"],
    }),
    new HtmlWebpackPlugin({ template: "./public/index.html" }),
  ],
};

webpack.prod.js

const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
 
module.exports = merge(common, {
  mode: "production",
  plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
    splitChunks: { chunks: "all" },
  },
});

webpack.dev.js

const { merge } = require("webpack-merge");
const common = require("./webpack.common");
 
module.exports = merge(common, {
  mode: "development",
  devServer: {
    port: 3001,
    historyApiFallback: true,
    hot: true,
  },
  devtool: "eval-source-map",
});

🌍 Using Environment Variables Across Environments

You’ll often want different values for API endpoints, feature flags, or keys in local, dev, and production environments.

Step 1: Create .env files in each project root


.env.development
.env.production
.env.local

Example content for .env.development:

REACT_APP_API_URL=http://localhost:4000

Step 2: Install dotenv plugin (for custom Webpack)

npm install dotenv-webpack --save-dev

Step 3: Configure in Webpack

const Dotenv = require("dotenv-webpack");
 
plugins: [new Dotenv({ path: "./.env.development" })];

Alternatively, in Create React App (CRA), variables prefixed with REACT_APP_ are auto-injected.

⚠️ Never include sensitive data like secrets or tokens in these files.


Environment-Specific Execution

To specify environment files dynamically in your commands, you can set the DOTENV_CONFIG_PATH:

npm install dotenv-cli --save-dev

Then update scripts like this:

"scripts": {
  "start:dev": "dotenv -e .env.development -- webpack serve --config webpack.dev.js",
  "start:prod": "dotenv -e .env.production -- webpack --config webpack.prod.js",
  "build:dev": "dotenv -e .env.development -- webpack --config webpack.dev.js",
  "build:prod": "dotenv -e .env.production -- webpack --config webpack.prod.js"
}

This ensures the right environment variables are injected based on the context of the command.


🐳 Basic Docker Setup

Dockerfile

FROM node:18 AS build
WORKDIR /app
COPY . .
RUN npm install && npm run build
 
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf

Build & Run

docker build -t mfe1 .
docker run -d -p 3001:80 mfe1

🌐 Basic NGINX Configuration

server {
  listen 80;
  server_name localhost;
 
  location / {
    root /usr/share/nginx/html;
    index index.html;
  }
 
  location /remoteEntry.js {
    add_header Cache-Control "no-cache";
  }
}

🔌 Essential Webpack Plugins

const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
 
plugins: [
  new HtmlWebpackPlugin({ template: "./public/index.html" }),
  new CleanWebpackPlugin(),
];

🚀 Production Optimization Techniques

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const webpack = require('webpack');
 
plugins: [
  new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }),
  new CompressionPlugin({ algorithm: 'gzip' }),
  new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
  new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
  new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }),
  new webpack.optimize.ModuleConcatenationPlugin(),
];
 
optimization: {
  minimize: true,
  minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
  splitChunks: { chunks: 'all' },
};

✅ Conclusion

Start with a working micro frontend setup, then incrementally layer in optimizations to make it production-grade. From module federation to caching strategies and bundling best practices—each part matters.

Note: "This is a initial explanation that i tried to put in an article, I'll be updating it with more details and code block in the future. Stay tuned for more!"