How I Built and Deployed Micro Frontends Using Webpack, Module Federation, Docker, and NGINX
A step-by-step guide on how I implemented micro frontends with Webpack Module Federation, Docker, and NGINX for scalable React applications.
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:
mfe-host(container app + shared components/utilities)mfe1(remote)mfe2(remote)
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:
webpack.common.jswebpack.dev.jswebpack.prod.js
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:4000Step 2: Install dotenv plugin (for custom Webpack)
npm install dotenv-webpack --save-devStep 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-devThen 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.confBuild & 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.