Arsitektur Micro-Frontend dengan Module Federation dan Nginx Reverse Proxy
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap rekayasa perangkat lunak enterprise modern, Single Page Application (SPA) monolitik telah mencapai batas skalabilitasnya. Ketika sebuah aplikasi web berskala enterprise berkembang hingga melibatkan puluhan tim lintas fungsi (cross-functional teams), basis kode tunggal (monorepo maupun polyrepo monolitik) berubah menjadi penghambat kecepatan rilis (deployment velocity). Masalah-masalah struktural mulai bermunculan:
- Build Time Degradation: Waktu kompilasi CI/CD membengkak secara eksponensial. Sedikit perubahan kode pada modul profil pengguna dapat memicu proses re-bundling seluruh aplikasi selama 20 hingga 45 menit.
- Blast Radius yang Luas: Bug regresi fatal yang diperkenalkan oleh tim domain pembayaran (payment) memiliki kapasitas untuk melumpuhkan seluruh ekosistem aplikasi web, menghentikan modul navigasi, autentikasi, hingga katalog produk.
- Pelanggaran Hukum Conway (Conway's Law): Struktur arsitektur monolitik memaksa tim frontend yang heterogen berkoordinasi secara ketat dan sinkron, menciptakan meeting fatigue, konflik git merge harian, dan hilangnya otonomi teknologi.
- Bundle Bloat & Runtime Performance Penalty: SPA monolitik cenderung mengakumulasi pustaka pihak ketiga (third-party libraries) yang tumpang tindih. Pengguna dipaksa mengunduh vendor bundle berukuran 5MB hingga 15MB pada inisialisasi awal, memperburuk metrik Core Web Vitals, khususnya Largest Contentful Paint (LCP) dan Interaction to Next Paint (INP).
Sebelum hadirnya Webpack 5 Module Federation, industri mencoba mengatasi fragmentasi frontend menggunakan pendekatan iFrames atau integrasi berbasis paket NPM (build-time integration). Namun, kedua pendekatan tersebut memiliki kompromi fatal:
- iFrames: Menyediakan isolasi sempurna, namun merusak user experience (UX), mempersulit sinkronisasi rute URL, merusak deep-linking, memicu duplikasi konsumsi memori browser (isolated rendering context), dan menjadi mimpi buruk bagi SEO serta aksesibilitas.
- Build-time NPM Packages: Memecah kode menjadi pustaka independen, namun tetap mewajibkan re-build dan re-deploy aplikasi penampung (shell application) setiap kali terjadi rilis mikro pada pustaka dependen. Ini bukan micro-frontend sejati, melainkan distributed monolith.
Perbandingan Kondisi: Monolitik vs Module Federation + Nginx
| Parameter Metrik | Monolithic SPA | Build-time Micro-Frontend (NPM) | Module Federation + Nginx Edge Proxy |
|---|---|---|---|
| Siklus Deployment | Terkunci sinkron (lockstep deployment) | Semi-independen (butuh bump version host) | Sepenuhnya independen (autonomous deployment) |
| Waktu Build CI/CD | Lambat (15 - 45+ menit) | Sedang pada modul, lambat pada host | Cepat (< 2 - 5 menit per remote) |
| Runtime Dependency Sharing | Otomatis via bundler tunggal | Risiko tinggi duplikasi versi dependensi | Dinamis pada runtime via Shared Scope |
| Ukuran Initial Bundle | Sangat besar (Monolithic bundle) | Besar (akibat duplikasi package) | Optimal (hanya shell + remote chunk terkait) |
| Isolasi Kegagalan (Fault) | Nol (Crash satu = crash semua) | Rendah | Tinggi (didukung React Error Boundary) |
| Routing & Ingress Control | Sisi Klien (Client-side routing) | Sisi Klien | Hybrid (Edge path routing via Nginx + Client) |
Dengan memadukan Webpack Module Federation pada lapisan aplikasi dan Nginx Reverse Proxy pada lapisan infrastruktur edge, kita dapat mencapai pemisahan domain yang bersih, orkestrasi dependensi runtime yang deterministik, performa transmisi aset yang terakselerasi, serta isolasi kegagalan tingkat enterprise.
2. Konsep Inti & Deep Dive Arsitektur
Webpack Module Federation bekerja pada paradigma kompilasi JavaScript tingkat lanjut yang memungkinkan sebuah bundler Webpack mengekspos dan mengonsumsi kode dari build terpisah secara dinamis pada saat runtime tanpa perlu kompilasi ulang.
Komponen Utama Module Federation
- Host (Container Shell): Aplikasi web utama yang bertanggung jawab menginisialisasi lingkungan global, mengelola state otentikasi global, menyediakan kerangka tata letak (layout shell), dan mengunduh antarmuka mikro secara dinamis.
- Remote: Aplikasi frontend independen yang mengompilasi dan mengekspos modul/komponen tertentu ke publik melalui berkas manifest runtime khusus bernama
remoteEntry.js. - Shared Scope (
__webpack_share_scopes__): Ruang penyimpanan global pada tingkat JavaScript runtime window di mana Host dan Remote mendaftarkan versi dependensi yang mereka bawa (misalnya React, React-DOM, UI Library). Webpack menggunakan algoritma negosiasi SemVer untuk menentukan apakah suatu dependensi dapat dipakai bersama (singleton) atau harus memuat salinan terpisah jika terdapat konflik versi mayor. - Remote Container Entry (
remoteEntry.js): Manifest JavaScript ringan yang berisi tabel pemetaan (manifest table) chunk, hash aset, dan fungsi getter asynchronous untuk memicu pemuatan chunk kode remote yang sesungguhnya secara on-demand.
Alur Transmisi & Eksekusi Data
Nginx bertindak sebagai gerbang terdepan (Edge Ingress & Reverse Proxy). Nginx menerima request dari klien browser, melakukan routing berbasis path atau domain ke upstream origin yang sesuai (Host Shell, Remote Catalog, atau Remote Checkout), mengelola validasi SSL/TLS termination, serta menerapkan strategi caching HTTP yang ketat untuk manifest vs aset ter-hash.
+-----------------------+
| Klien Browser |
+-----------+-----------+
|
HTTPS (Port 443)
|
v
+---------------------------------------------+
| Nginx Reverse Proxy / Edge Router |
| (Load Balancing & Ingress) |
+------+--------------+----------------+------+
| | |
Path: / | Path: /cat/ | Path: /cart/ |
(Host Container) | (Remote 1) | (Remote 2) |
v v v
+-------------+ +-------------+ +-------------+
| Host Shell | | Catalog App | | Checkout App|
| Node/Static | | Node/Static | | Node/Static |
| Port: 3000 | | Port: 3001 | | Port: 3002 |
+------+------+ +------+------+ +------+------+
| | |
+---------------+---------------+
|
Runtime Shared Scope Binding
|
v-------------------------------v
| Browser Window Environment |
| - React 18 (Singleton Shared) |
| - React Router Dom (Shared) |
| - __webpack_share_scopes__ |
+-------------------------------+
Pada saat browser mengakses halaman utama, alur eksekusi internal berjalan sebagai berikut:
- Browser mengirim HTTP GET
/ke Nginx. Nginx mengarahkan request ke upstream Host Container. - Browser mengunduh
index.htmldanmain.jsmilik Host. - Host menginisialisasi dependensi global dan membuat async bootstrap barrier (
import('./bootstrap')). Langkah ini krusial agar inisialisasi shared scope selesai sebelum kode aplikasi utama dieksekusi. - Ketika Host merender rute
/catalog, Host memanggil dynamic import remote script. Browser mengirim request ke Nginx pada path/catalog-remote/remoteEntry.js. - Nginx mem-proxy request tersebut ke upstream Catalog App (Port 3001).
- Host mengevaluasi manifest
remoteEntry.js, menegosiasikan versi shared dependencies (misal: React). Jika versi cocok, Remote Catalog akan langsung menggunakan instance React yang sudah dimuat oleh Host di memori browser tanpa mengunduh React kembali. - Modul remote di-mount ke Virtual DOM Host di dalam pembungkus
React.SuspensedanErrorBoundary.
3. Perbandingan Komparatif / Analisis Pendekatan
Sebelum memutuskan arsitektur, arsitek sistem harus memahami secara matematis dan teknis trade-off dari berbagai pola arsitektur micro-frontend.
| Fitur / Parameter | iFrames | Single-SPA (In-Browser ESM) | Module Federation (Webpack 5) | Server-Side Includes (SSI/ESI) via Nginx |
|---|---|---|---|---|
| Kompleksitas Operasional | Sangat Rendah | Sedang | Sedang - Tinggi | Tinggi (Logika state di edge) |
| Overhead CPU Klien | Sangat Tinggi (DOM instance ganda) | Rendah | Sangat Rendah | Sangat Rendah |
| Konsumsi RAM Browser | Ekstrem (Isolasi context per iframe) | Rendah | Sangat Rendah (Shared module sharing) | Rendah |
| Latensi Inisialisasi | Lambat | Cepat | Sangat Cepat (Asynchronous parallel chunk) | Tercepat (Pre-rendered edge) |
| Resiliensi Crash | Total (Crash iframe tidak merusak host) | Rentan jika unhandled exception | Tinggi (Bila menggunakan React Error Boundary) | Nol (Gagal fragmen = HTTP 502/500) |
| Dukungan SSR | Buruk | Terbatas | Sangat Baik (Next.js/Node Federation) | Native |
| CSS Encapsulation | Native | Manual (Scoped CSS / Shadow DOM) | Manual / PostCSS namespace prefixing | Manual |
Analisis Trade-off Sumber Daya:
- CPU & Memori Browser: Module Federation adalah pemenang mutlak dibanding iFrames. Module Federation menyatukan tree DOM dalam satu thread komputasi tanpa overhead context switching v8 engine lintas context. Memory footprint dapat dipangkas hingga 70% dibandingkan pola multi-iFrame.
- Network Latency & Bandwidth: Melalui mekanisme Shared Scope, jika Host dan 5 Remote sama-sama membutuhkan
lodash, hanya 1 salinanlodashseberat ~25KB gzipped yang diunduh dari network. Bandwidth jaringan klien dihemat secara dramatis.
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum melangkah ke implementasi kode, pastikan lingkungan infrastruktur memenuhi spesifikasi enterprise berikut:
Spesifikasi Minimum Lingkungan Produksi:
- Operating System: Ubuntu 22.04 LTS / Debian 12 Bookworm / RHEL 9
- Node.js: v18.19.0 LTS atau v20.11.0 LTS
- Package Manager: Yarn v1.22+ atau PNPM v8.15+ (Direkomendasikan PNPM untuk deduplikasi dependensi monorepo)
- Webpack: Core Webpack v5.89.0 atau lebih baru
- Web Server: Nginx v1.24.0 (Stable) atau v1.25.x (Mainline) yang dikompilasi dengan modul
ngx_http_gzip_moduledanngx_http_brotli_module(opsional namun sangat disarankan) - Akses Port Jaringan: Port 80 (HTTP), Port 443 (HTTPS), Port internal upstream (3000, 3001, 3002)
Struktur Direktori Proyek Monorepo / Multi-App:
/opt/enterprise-mfe/
├── nginx/
│ └── conf.d/
│ └── microfrontends.conf
├── apps/
│ ├── host-shell/
│ │ ├── package.json
│ │ ├── webpack.config.js
│ │ └── src/
│ │ ├── index.js
│ │ ├── bootstrap.js
│ │ ├── App.js
│ │ └── declarations.d.ts
│ ├── remote-catalog/
│ │ ├── package.json
│ │ ├── webpack.config.js
│ │ └── src/
│ │ ├── index.js
│ │ ├── bootstrap.js
│ │ └── CatalogList.js
│ └── remote-checkout/
│ ├── package.json
│ ├── webpack.config.js
│ └── src/
│ ├── index.js
│ ├── bootstrap.js
│ └── CartWidget.js
└── docker-compose.yml
5. Panduan Implementasi Langkah demi Langkah
Langkah 1: Konfigurasi Webpack Remote (remote-catalog)
Remote aplikasi ini mengekspos komponen CatalogList kepada siapapun yang memvalidasi kontrak federasi.
Berkas: apps/remote-catalog/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
publicPath: '/catalog-assets/',
clean: true,
},
resolve: {
extensions: ['.jsx', '.js', '.json'],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'remote_catalog',
filename: 'remoteEntry.js',
exposes: {
'./CatalogList': './src/CatalogList',
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
strictVersion: true,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
strictVersion: true,
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Implementasikan pola Async Bootstrap Entry Point pada Remote:
Berkas: apps/remote-catalog/src/index.js
// Async boundary pattern yang wajib digunakan oleh Webpack Module Federation
import('./bootstrap');
Berkas: apps/remote-catalog/src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import CatalogList from './CatalogList';
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(<CatalogList standalone={true} />);
}
Berkas: apps/remote-catalog/src/CatalogList.js
import React, { useState, useEffect } from 'react';
const CatalogList = ({ standalone = false }) => {
const [items, setItems] = useState([]);
useEffect(() => {
// Simulasi pengambilan data inventaris produk
setItems([
{ id: 'SKU-01', name: 'Cloud Infrastructure Server Enterprise', price: 4500000 },
{ id: 'SKU-02', name: 'High-Availability Load Balancer Hardware', price: 8200000 },
{ id: 'SKU-03', name: 'Managed Distributed Database Cluster', price: 6100000 },
]);
}, []);
return (
<div style={{ border: '2px dashed #0284c7', padding: '1rem', borderRadius: '8px' }}>
<h3 style={{ color: '#0284c7', marginTop: 0 }}>
Micro-Frontend: Domain Katalog {standalone && '(Mode Mandiri / Standalone)'}
</h3>
<ul style={{ listStyleType: 'none', padding: 0 }}>
{items.map((item) => (
<li key={item.id} style={{ padding: '8px 0', borderBottom: '1px solid #e2e8f0' }}>
<strong>{item.name}</strong> - Rp {item.price.toLocaleString('id-ID')}
</li>
))}
</ul>
</div>
);
};
export default CatalogList;
Langkah 2: Konfigurasi Webpack Host Container (host-shell)
Host mengonsumsi remoteEntry.js milik remote_catalog.
Berkas: apps/host-shell/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
publicPath: '/',
clean: true,
},
resolve: {
extensions: ['.jsx', '.js', '.json'],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'host_shell',
remotes: {
// Format: remoteAlias: 'remoteInternalName@URL_PATH'
remote_catalog: 'remote_catalog@/catalog-assets/remoteEntry.js',
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
strictVersion: true,
},
'react-dom': {
singleton: true,
requiredVersion: deps['react-dom'],
strictVersion: true,
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
Entry point Host:
Berkas: apps/host-shell/src/index.js
import('./bootstrap');
Berkas: apps/host-shell/src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
if (container) {
const root = createRoot(container);
root.render(<App />);
}
Berkas: apps/host-shell/src/App.js
import React, { Suspense } from 'react';
// Lazy load modul remote dengan dynamic import Module Federation
const RemoteCatalogList = React.lazy(() => import('remote_catalog/CatalogList'));
// Class Component Error Boundary untuk menangani kegagalan jaringan pada remote
class MicroFrontendErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorLog: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, errorLog: error };
}
componentDidCatch(error, errorInfo) {
console.error('[MFE Infrastructure Error]: Gagal memuat Remote Catalog', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '1.5rem', backgroundColor: '#fef2f2', border: '1px solid #ef4444', borderRadius: '6px' }}>
<h4 style={{ color: '#b91c1c', margin: 0 }}>Layanan Katalog Sementara Tidak Tersedia</h4>
<p style={{ color: '#7f1d1d', fontSize: '0.875rem' }}>
Modul gagal diinisialisasi oleh browser. Host Container tetap beroperasi secara normal.
</p>
</div>
);
}
return this.props.children;
}
}
const App = () => {
return (
<div style={{ fontFamily: 'system-ui, -apple-system, sans-serif', maxWidth: '1000px', margin: '0 auto', padding: '2rem' }}>
<header style={{ borderBottom: '2px solid #e2e8f0', paddingBottom: '1rem', marginBottom: '2rem' }}>
<h1 style={{ color: '#0f172a' }}>Portal Enterprise Shell (Host Container)</h1>
<span style={{ backgroundColor: '#22c55e', color: '#fff', padding: '4px 8px', borderRadius: '4px', fontSize: '0.75rem' }}>
Sistem Aktif
</span>
</header>
<main>
<section style={{ marginBottom: '2rem' }}>
<h2>Dashboard Utama</h2>
<p>Bagian ini dirender sepenuhnya dari Host Container Shell.</p>
</section>
<section>
<MicroFrontendErrorBoundary>
<Suspense fallback={<div style={{ padding: '1rem', color: '#64748b' }}>Mengunduh chunk modul katalog...</div>}>
<RemoteCatalogList standalone={false} />
</Suspense>
</MicroFrontendErrorBoundary>
</section>
</main>
</div>
);
};
export default App;
Langkah 3: Konfigurasi Nginx Reverse Proxy & Asset Gateway
Konfigurasi Nginx ini bertindak sebagai satu-satunya Entry Point Public (Same-Origin), mengarahkan lalu lintas data, mengatur header keamanan WAF/CORS, dan mengimplementasikan aturan caching yang ketat.
Berkas: /etc/nginx/conf.d/microfrontends.conf
# Upstream definition untuk Host Shell dan Micro-Frontend Remotes
upstream upstream_host_shell {
server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
keepalive 32;
}
upstream upstream_remote_catalog {
server 127.0.0.1:3001 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name portal.enterprise.internal;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name portal.enterprise.internal;
# Konfigurasi Sertifikat SSL/TLS
ssl_certificate /etc/ssl/certs/enterprise-portal.crt;
ssl_certificate_key /etc/ssl/private/enterprise-portal.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Security Headers WAF & Hardening
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self';" always;
# Optimasi Kompresi Gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/atom+xml image/svg+xml;
# 1. Routing Manifest Remote: remoteEntry.js (CRITICAL: DILARANG KERAS DI-CACHE)
location = /catalog-assets/remoteEntry.js {
proxy_pass http://upstream_remote_catalog/remoteEntry.js;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Header anti-cache wajib untuk manifest manifest Module Federation
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
}
# 2. Routing Chunk Aset Statis Remote yang Menggunakan Content-Hash
location /catalog-assets/ {
# Menghapus prefiks /catalog-assets/ sebelum mengirim ke upstream remote
rewrite ^/catalog-assets/(.*)$ /$1 break;
proxy_pass http://upstream_remote_catalog;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Cache aset statis ter-hash secara permanen di browser dan edge proxy
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# 3. Routing Host Container Shell Application (Default Fallback)
location / {
proxy_pass http://upstream_host_shell;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Konfigurasi SPA HTML Fallback Cache
add_header Cache-Control "no-cache, must-revalidate";
}
# Custom Error Handling
error_page 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
internal;
}
}
Langkah 4: Orkestrasi Menggunakan Docker Compose
Untuk menguji interaksi sistem secara utuh di server lokal maupun lingkungan staging, kita memanfaatkan Docker Compose.
Berkas: docker-compose.yml
version: '3.8'
services:
nginx-ingress:
image: nginx:1.25-alpine
container_name: mfe-nginx-gateway
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/ssl:ro
depends_on:
- host-shell
- remote-catalog
restart: always
networks:
- mfe-network
host-shell:
build:
context: ./apps/host-shell
dockerfile: Dockerfile
container_name: mfe-host-shell
expose:
- "3000"
environment:
- PORT=3000
networks:
- mfe-network
remote-catalog:
build:
context: ./apps/remote-catalog
dockerfile: Dockerfile
container_name: mfe-remote-catalog
expose:
- "3001"
environment:
- PORT=3001
networks:
- mfe-network
networks:
mfe-network:
driver: bridge
6. Bedah Kode & Penjelasan Detail Baris Kunci
Memahami implikasi dari setiap baris konfigurasi sangat penting agar arsitektur ini tidak kolaps saat beban trafik melonjak tajam.
1. Inisialisasi Async Boundary: import('./bootstrap');
[!IMPORTANT] Jika Anda langsung mengeksekusi kode React (
createRoot(..).render(..)) di dalam berkas entry point utama (index.js) tanpa membungkusnya dalam dynamic asynchronous import, Webpack Module Federation akan memunculkan error fatal pada konsol browser:Uncaught Error: Shared module is not available for eager consumption: react.
Penyebab mendasar: Webpack memerlukan setidaknya satu siklus Event Loop tick asynchronous untuk mengeksekusi fungsi negosiasi SemVer di tabel __webpack_share_scopes__. Berkas index.js bertindak sebagai inisiator penataan dependensi, sedangkan bootstrap.js baru dieksekusi setelah semua dependensi shared selesai dikonsiliasi.
2. Parameter ModuleFederationPlugin:
name: Nama namespace internal runtime yang didaftarkan ke variabel global window browser. Pada remote catalog, ini adalahremote_catalog.filename: 'remoteEntry.js': Menentukan nama berkas manifest yang di-generate. Berkas ini berukuran sangat kecil (biasanya < 5KB - 15KB) karena hanya berisi pointer resolver.exposes: Menentukan kontrak antarmuka (API surface). Kunci'./CatalogList'adalah identifier virtual yang akan dipanggil oleh Host via sintaksimport('remote_catalog/CatalogList'). Nilai'./src/CatalogList'adalah path fisik berkas komponen di sistem lokal.sharedconfiguration:singleton: true: Memastikan hanya satu instance tunggal dari pustaka ini yang boleh dimuat di memori browser. Ini adalah parameter wajib untuk pustaka yang mengelola internal state atau event loop berbasis referensi memori, sepertireactdanreact-dom.strictVersion: true: Menginstruksikan Webpack untuk menolak memuat remote jika versinya tidak kompatibel denganrequiredVersion. Jikafalse, Webpack akan melakukan fallback dengan memuat salinan kedua (yang berisiko memunculkan bug React Invalid Hook Call Warning).
3. Arsitektur Caching pada Nginx Ingress:
location = /catalog-assets/remoteEntry.js:- Menggunakan operator exact match (
=) sehingga memiliki prioritas pencocokan tertinggi di Nginx. add_header Cache-Control "no-store, no-cache, must-revalidate...": Aturan emas Module Federation. ManifestremoteEntry.jsberisi referensi hash ke chunk-chunk JavaScript terbaru ([name].[contenthash].js). Jika berkas manifest ini tersimpan di cache browser pengguna selama 1 jam saja, deployment fitur baru di Remote akan memicu error HTTP 404 pada browser klien karena browser mencoba mengunduh chunk lama yang sudah dihapus oleh build baru di server.
- Menggunakan operator exact match (
location /catalog-assets/:- Mengatur header
Cache-Control: public, max-age=31536000, immutable. - Chunk aset yang dipanggil oleh manifest memiliki hash unik (misal:
CatalogList.8f2d1e.js). Jika kode berubah, hash otomatis berubah. Oleh karena itu, chunk ini aman di-cache secara permanen selama 1 tahun (31536000 detik), mengurangi beban hit ke upstream server hingga 95%.
- Mengatur header
7. Skenario Produksi & Studi Kasus Dunia Nyata
Studi Kasus: Zero-Downtime Deployment Tanpa Restart Host
Bayangkan sebuah platform e-commerce dengan trafik 80.000 Request Per Detik (RPS). Tim domain Checkout ingin merilis sistem pembayaran baru menggunakan metode cicilan instan.
Dalam arsitektur monolitik konvensional:
- Tim Checkout harus membuka Pull Request ke monorepo.
- Tim Host harus memverifikasi bahwa perubahan checkout tidak merusak modul landing page.
- Pipeline CI/CD memakan waktu 35 menit.
- Server host container harus di-rolling update.
Dalam Arsitektur Module Federation + Nginx:
[ CI/CD Pipeline Remote Checkout ]
|
1. Build: `webpack --mode production`
2. Output: `dist/CartWidget.99ab4c.js`
`dist/remoteEntry.js`
|
3. Upload ke Storage / S3 / Rolling Pod
v
+---------------------------------+
| Remote Checkout Server (Port 3002|
+---------------------------------+
^
|
+-------------------------------+-------------------------------+
| |
Klien A (Sesi Lama) Klien B (Sesi Baru)
Membaca `remoteEntry.js` v1 Membaca `remoteEntry.js` v2
Mengakses `CartWidget.11aa2b.js` Mengakses `CartWidget.99ab4c.js`
(Berjalan di cache browser) (Diambil langsung dari Nginx)
- Tim Checkout melakukan rilis hanya pada direktori
apps/remote-checkout. - Pipeline CI/CD hanya memerlukan waktu 90 detik untuk menghasilkan chunk baru.
- Berkas-berkas baru disalin ke upstream Remote Checkout (
127.0.0.1:3002). - Klien baru yang memuat portal secara otomatis mengambil
remoteEntry.jsbaru (karena instruksi NginxCache-Control: no-cache). - Host Container (
127.0.0.1:3000) tidak disentuh sama sekali, tidak ada restart container, dan tidak ada downtime.
Mitigasi Bencana Jaringan (Network Partition Fault-Tolerance)
Jika server remote-catalog down secara tiba-tiba (misalnya OOM Crash pada Pod): Nginx mengembalikan kode HTTP 502/504 untuk request /catalog-assets/remoteEntry.js.
Komponen MicroFrontendErrorBoundary pada Host Container menangkap exception JavaScript runtime tersebut, menghentikan propagasi White Screen of Death, dan menampilkan fallback UI yang anggun (graceful degradation). Pengguna tetap bisa melakukan login, mencari artikel, dan bertransaksi di modul lain tanpa hambatan.
8. Common Pitfalls, Anti-Patterns & Solusi
1. The Duplicate React Instance Nightmare
- Gejala: Muncul pesan error di console:
Uncaught TypeError: Cannot read property 'useMemo' of nullatauInvalid Hook Call Warning. - Penyebab Akar: Remote mengompilasi salinan React versinya sendiri, atau Host memuat React v18.2.0 sedangkan Remote menggunakan React v18.1.0 tanpa deklarasi
singleton: truepada ModuleFederationPlugin. Ketika remote dipanggil, ada dua salinan React terpisah yang aktif di window browser. - Solusi: Pastikan deklarasi berikut ada pada seluruh
webpack.config.jsmilik Host dan seluruh Remote:
shared: {
react: {
singleton: true,
strictVersion: true,
requiredVersion: '^18.2.0'
},
'react-dom': {
singleton: true,
strictVersion: true,
requiredVersion: '^18.2.0'
}
}
2. Zombie Cache / Stale Entry Collision
- Gejala: Setelah deployment remote, pengguna melaporkan error
Loading chunk failedatauChunkLoadError: Loading chunk 842 failed. - Penyebab Akar: Nginx atau CDN (seperti Cloudflare) melakukan caching pada berkas
remoteEntry.jskarena header Cache-Control default server. Browser memegangremoteEntry.jslama yang merujuk ke chunk aset842.jsyang sudah dihapus dari server remote pada build terbaru. - Solusi: Wajib implementasikan blok
location = /.../remoteEntry.jsdengan direktifCache-Control "no-store, no-cache, must-revalidate"persis seperti contoh Nginx di atas.
3. Global CSS Collisions & Style Bleeding
- Gejala: Tombol di Host berubah menjadi merah karena Remote mengimpor berkas CSS global
button { background: red; }. - Anti-Pattern: Menggunakan CSS global tanpa scoping di dalam aplikasi remote.
- Solusi Rekomendasi: Wajibkan standardisasi arsitektur styling pada seluruh tim mikro-frontend:
- Gunakan CSS Modules (
styles.module.css). - Gunakan Tailwind CSS dengan konfigurasi
prefix: 'mfe-catalog-'ditailwind.config.jsmasing-masing remote. - Gunakan pustaka CSS-in-JS dengan isolation scoping yang aktif.
- Gunakan CSS Modules (
4. Routing Desynchronization
- Gejala: Mengklik navigasi di dalam Remote tidak memperbarui URL bar browser, atau tombol back browser keluar dari aplikasi.
- Penyebab Akar: Remote menginisialisasi
BrowserRoutermiliknya sendiri di dalam modul. Di browser hanya boleh ada satuBrowserRouteraktif. - Solusi: Remote harus mengekspos komponen yang menggunakan
MemoryRoutersaat berjalan secara terisolasi (mode mandiri), atau mengandalkan Context Router yang dipasok oleh Host Shell (menggunakan delegated routing).
9. Benchmark, Metrik Kinerja & Tuning Performa
Untuk membuktikan keunggulan arsitektur ini dibanding monolithic build, pengujian beban (load testing) dan analisis performa runtime dilakukan di lingkungan terkontrol.
Lingkungan Pengujian Benchmark
- Spesifikasi Server Ingress: 4 vCPU, 8 GB RAM, Ubuntu 22.04 LTS
- Tool Pengujian Beban:
k6versi 0.48.0 - Skenario: 2.000 Virtual Users (VU) konkuren melakukan hit ke gateway selama 5 menit.
Script Pengujian K6 (stress-test.js)
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '1m', target: 500 },
{ duration: '3m', target: 2000 },
{ duration: '1m', target: 0 },
],
thresholds: {
http_req_duration: ['p(95)<150'], // 95% request harus selesai di bawah 150ms
http_req_failed: ['rate<0.01'], // Error rate di bawah 1%
},
};
export default function () {
const BASE_URL = 'https://portal.enterprise.internal';
// 1. Ambil HTML Host Shell
const resHtml = http.get(`${BASE_URL}/`);
check(resHtml, { 'Host HTML status 200': (r) => r.status === 200 });
// 2. Ambil Manifest Remote (Anti-Cache)
const resManifest = http.get(`${BASE_URL}/catalog-assets/remoteEntry.js`);
check(resManifest, {
'RemoteEntry status 200': (r) => r.status === 200,
'Anti-cache header terpasang': (r) => r.headers['Cache-Control'].includes('no-cache'),
});
sleep(1);
}
Hasil Benchmark Transmisi Nginx
| Metrik Performa | Monolith SPA Bundle (Tunggal) | Micro-Frontend via Nginx Proxy |
|---|---|---|
| Throughput (Requests/Sec) | 4.210 RPS | 18.750 RPS |
| Latensi Rata-rata (p95) | 480 ms | 38 ms |
| Initial Bundle Download Size | 4.8 MB | 140 KB (Host) + 28 KB (Remote) |
| Largest Contentful Paint (LCP) | 3.8 detik | 1.1 detik |
| Cumulative Layout Shift (CLS) | 0.18 | 0.01 |
Parameter Tuning Lanjutan Nginx untuk High-Concurrency
Tambahkan blok berikut pada berkas /etc/nginx/nginx.conf untuk memaksimalkan throughput ingress:
# Optimasi Worker Core & Koneksi
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 16384;
use epoll;
multi_accept on;
}
http {
# Optimasi Socket & I/O
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
# In-Memory Cache untuk File Descriptors
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Buffer Tuning untuk Menghindari Disk Buffering
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
}
10. Checklist Penerapan Produksi & Kesimpulan
Sebelum menandatangani persetujuan rilis ke lingkungan produksi (Production Sign-off), pastikan seluruh item di bawah ini telah terverifikasi secara teknis:
Infrastructure & Nginx Gateway
- Berkas manifest
remoteEntry.jsmemiliki konfigurasi headerCache-Control: no-cache, no-store, must-revalidatedi Nginx. - Semua file aset ter-hash (
*.[contenthash].js,*.css) memiliki headerCache-Control: public, max-age=31536000, immutable. - Upstream Nginx mengaktifkan
keepaliveconnection untuk mereduksi overhead TCP handshake ke aplikasi pod internal. - Konfigurasi TLS 1.3 dan protokol HTTP/2 telah aktif dan lolos uji SSL Labs dengan grade A+.
- Mekanisme Fallback
error_page 502 503 504telah disiapkan agar tidak menampilkan halaman error default Nginx.
Webpack & Module Federation Runtime
- Semua aplikasi menggunakan pola Async Boundary (
import('./bootstrap')) pada entri utama JavaScript. -
reactdanreact-domdikonfigurasi sebagaisingleton: truedanstrictVersion: truepada Host dan semua Remote. - Komponen remote yang dipanggil di Host selalu dibungkus di dalam
React.SuspensedanErrorBoundarykustom. - Variabel
publicPathpada Remote disetel secara absolut atau menggunakan path prefix yang sesuai dengan routing reverse proxy Nginx (misal:/catalog-assets/).
Observability & Security
- Content Security Policy (CSP) mengizinkan eksekusi chunk script yang sah dan memblokir domain luar yang tidak diotorisasi.
- Distributed tracing (seperti OpenTelemetry) disuntikkan ke HTTP Request Headers (
traceparent) lintas Host dan Remote. - Sistem monitoring error client-side (seperti Sentry) dapat mendeteksi kegagalan chunk load secara real-time.
Kesimpulan Arsitektur
Kombinasi Webpack Module Federation dan Nginx Reverse Proxy menghadirkan keseimbangan ideal antara otonomi tim pengembang dan efisiensi infrastruktur.
Dengan memindahkan kendali resolusi modul ke runtime browser menggunakan algoritma Shared Scope, organisasi dapat mengeliminasi fragmentasi dependensi tanpa mengorbankan kecepatan deployment independen. Sementara itu, menempatkan Nginx sebagai Edge Router menyelesaikan masalah Cross-Origin Resource Sharing (CORS), mengoptimalkan pengiriman aset ter-hash secara deterministik, dan menyediakan pagar pengaman jaringan kelas enterprise. Arsitektur ini adalah fondasi yang kokoh untuk membawa ekosistem frontend skala enterprise bertumbuh secara tangguh dan berkelanjutan.