L
React • Literally Web ID

Arsitektur Micro-Frontends dengan React dan Module Federation di Lingkungan Enterprise

⏱️ 18 menit baca • 📅 16 Oct 2026 • 👁️ 1 views
Arsitektur Micro-Frontends dengan React dan Module Federation di Lingkungan Enterprise

1. Konteks Industri & Analisis Akar Masalah

Di dalam lanskap rekayasa perangkat lunak enterprise modern, monolith frontend—sering disebut sebagai Mega-SPA (Single Page Application)—telah mencapai batas skalabilitas organisasional dan teknisnya. Ketika puluhan atau bahkan ratusan engineer dari berbagai tim otonom (tribe/stream-aligned teams) mencoba mendeploy fitur ke dalam satu codebase React yang sama, kolaborasi berubah menjadi mimpi buruk administratif. Permasalahan utama yang muncul meliputi konflik manajemen state global, waktu kompilasi (bundle build time) yang memakan waktu belasan menit, siklus rilis (release cadence) yang saling menghambat karena terikat pada satu deployment pipeline tunggal, serta risiko regression bug yang masif di mana kegagalan pada satu modul kecil dapat meruntuhkan seluruh aplikasi.

Secara historis, industri mencoba mengatasi hal ini melalui iframe-based isolation atau multi-page application tradisional. Namun, pendekatan iframe gagal dalam memberikan pengalaman pengguna yang mulus (seamless UX) karena masalah manajemen fokus, isolasi DOM yang kaku, serta kesulitan berbagi state secara reaktif antar komponen. Sebaliknya, kembali ke arsitektur multi-page menghancurkan konsep interaktivitas aplikasi web modern yang instan.

Di sinilah Module Federation—sebuah fitur revolusioner yang dipelopori oleh tim Webpack (dan kini diadopsi oleh ekosistem Vite melalui plugin khusus)—mengubah paradigma arsitektur frontend. Dengan Module Federation, aplikasi React dipecah menjadi unit-unit otonom yang disebut Host dan Remote. Host bertindak sebagai shell atau kontainer utama, sementara Remote adalah aplikasi mikro mandiri yang dapat dikompilasi, diuji, dan dideploy secara independen (decoupled deployment), namun dapat dimuat secara dinamis ke dalam ruang memori runtime Host tanpa perlu proses re-bundling pada aplikasi utama.

Perbandingan Kondisi: Sebelum vs Sesudah Penerapan

Parameter Sebelum (Monolithic React SPA) Sesudah (Micro-Frontends via Module Federation)
Waktu Build (CI/CD) Lambat (15 - 45 menit untuk seluruh aplikasi) Cepat & Terisolasi (1 - 3 menit per Remote App)
Siklus Rilis Terikat (Harus rilis bersamaan dalam satu versi) Independen (Setiap tim merilis kapan saja secara mandiri)
Skala Tim Sering terjadi git merge conflicts, koordinasi kompleks Otonom, setiap tim memiliki kepemilikan penuh atas repo masing-masing
Penggunaan Bandwidth Mengunduh seluruh bundle raksasa di awal Memuat modul secara on-demand (lazy loading) hanya saat dibutuhkan
Ketahanan Sistem Single point of failure (Satu error merusak seluruh app) Graceful degradation (Error di satu Remote tertangkap oleh Error Boundary)

2. Konsep Inti & Deep Dive Arsitektur

Module Federation bekerja dengan mengubah asumsi dasar bundler JavaScript tradisional. Alih-alih menganggap dependency graph sebagai satu kesatuan tertutup yang harus diselesaikan pada waktu build, Module Federation memperkenalkan konsep Host (konsumen modul) dan Remote (produsen modul) yang berkomunikasi secara asinkron pada waktu runtime melalui jaringan.

Alur Eksekusi & Data Flow di Balik Layar

  1. Inisialisasi Host: Pengguna mengakses URL aplikasi utama. Browser mengunduh shell bundle dari server Host.
  2. Resolusi Remote Entry: Saat rute tertentu diakses, Host mendeteksi bahwa komponen yang dibutuhkan berada di dalam Remote App. Host melakukan fetch terhadap file manifes Remote yang dikenal sebagai remoteEntry.js dari server Remote (misalnya disajikan melalui CDN atau Nginx).
  3. Evaluasi Container: remoteEntry.js mengekspos objek global (biasanya dinamai sesuai nama remote) yang berisi peta modul yang tersedia beserta fungsi async import-nya.
  4. Manajemen Shared Dependencies: Host dan Remote melakukan negosiasi versi pustaka bersama (seperti react dan react-dom). Jika versi cocok atau memenuhi rentang semver yang diizinkan, modul bersama hanya dimuat sekali dari Host (singleton enforcement), mencegah duplikasi ukuran bundle yang membengkak.
  5. Mounting & Rendering: Komponen dari Remote dirender ke dalam DOM pohon komponen React Host melalui mekanisme React.lazy dan Suspense.
+-------------------------------------------------------------------------+
|                        BROWSER (Runtime Client)                         |+
|                                                                         |
|  +-------------------------------------------------------------------+  |
|  |                         HOST APPLICATION                          |  |
|  |                                                                   |  |
|  |  +------------------+     +------------------+                    |  |
|  |  |   App Shell      |     |  Router / State  |                    |  |
|  |  +--------+---------+     +--------+---------+                    |  |
|  |           |                        |                              |
|  |           +------------+-----------+                              |
|  |                        |                                          |
|  |                        v                                          |
|  |         [ React.lazy & Suspense Boundary ]                        |
|  +------------------------+------------------------------------------+
|                           |                                         
|                           | HTTP / Dynamic Import                   
|                           v                                         
|  +------------------------+------------------------------------------+
|  |                        |                                          |
|  |                        v                                          |
|  |  +------------------------------------+                           |
|  |  | REMOTE APP: remoteEntry.js         |                           |
|  |  +-----------------+------------------+                           |
|  |                    |                                              |
|  |                    v                                              |
|  |  +------------------------------------+                           |
|  |  | Exposed Components (e.g., Widget)  |                           |
|  |  +------------------------------------+                           |
|  |                        REMOTE APPLICATION                         |
|  +-------------------------------------------------------------------+
+-------------------------------------------------------------------------+

3. Perbandingan Komparatif / Analisis Pendekatan

Untuk memilih strategi micro-frontend yang tepat di level enterprise, arsitek harus memahami perbandingan performa dan kompleksitas antara Module Federation dengan pendekatan lain:

Kriteria Arsitektur Webpack/Vite Module Federation IFrame Isolation Build-time Integration (NPM Packages)
Runtime Performance Tinggi (Shared dependencies, DOM native) Rendah (Overhead memori tinggi, DOM terisolasi) Tinggi (Native bundle)
Bundle Size Duplication Sangat Rendah (Berbagi singleton dependencies) Tinggi (Setiap iframe memuat runtime React sendiri) Tinggi (Jika tidak dikelola dengan sangat hati-hati)
Komunikasi Antar App Mudah (React Context, Custom Events, Zustand/Redux) Kompleks (window.postMessage, event listener) Kompleks (Harus re-publish npm package setiap ada perubahan)
Kompleksitas Setup Menengah ke Tinggi (Membutuhkan konfigurasi bundler khusus) Sangat Rendah (HTML murni) Rendah pada awalnya, tinggi di kemudian hari (Versioning hell)
Isolated Deployment Ya (Sangat mulus) Ya Tidak (Harus build ulang Host)

[!IMPORTANT] Jangan pernah menggunakan pendekatan Build-time Integration (NPM Package) jika tujuan utama Anda adalah kebebasan deployment tim yang otonom. NPM package memaksa Host untuk melakukan re-build dan re-deploy setiap kali ada pembaruan pada modul anak.


4. Prasyarat Sistem & Persiapan Lingkungan

Sebelum menyelami kode, pastikan infrastruktur lokal dan produksi Anda memenuhi standar spesifikasi teknis berikut:

  • Node.js: Versi 18.x atau 20.x LTS (disarankan menggunakan nvm untuk manajemen versi).
  • Package Manager: npm (v9+) atau pnpm (v8+) untuk manajemen workspace monorepo/multirepo.
  • Web Server / Proxy (Nginx/Caddy): Diperlukan di lingkungan produksi untuk menangani header CORS (Cross-Origin Resource Sharing) yang tepat karena file remoteEntry.js diakses lintas domain/subdomain.
  • Struktur Port Lokal:
    • Host Application: http://localhost:3000
    • Remote Application (Dashboard/Analytics): http://localhost:3001

5. Panduan Implementasi Langkah demi Langkah

Kita akan membangun dua aplikasi: HostApp (berjalan di port 3000) dan RemoteApp (berjalan di port 3001) menggunakan Webpack 5 Module Federation. Di lingkungan enterprise, Webpack masih menjadi standar industri paling matang untuk modul federation.

Langkah 1: Inisialisasi Struktur Direktori

Buat dua direktori terpisah untuk merepresentasikan arsitektur multirepo:

mkdir -p enterprise-mf/host-app enterprise-mf/remote-app

Langkah 2: Konfigurasi Remote Application (remote-app)

Masuk ke direktori remote-app, inisialisasi proyek, dan instal dependensi dasar:

cd enterprise-mf/remote-app
npm init -y
npm install react@18.2.0 react-dom@18.2.0
npm install -D webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 html-webpack-plugin@5.6.0 babel-loader@9.1.3 @babel/core@7.23.9 @babel/preset-react@7.23.3

Buat file konfigurasi Babel babel.config.json:

{
  "presets": [
    "@babel/preset-react"
  ]
}

Buat komponen React yang akan diekspos di remote-app/src/Widget.jsx:

import React, { useState } from 'react';

export default function Widget() {
  const [count, setCount] = useState(0);

  return (
    <div style={{
      padding: '20px',
      backgroundColor: '#f0f4f8',
      borderRadius: '8px',
      border: '1px solid #cbd5e1',
      fontFamily: 'sans-serif'
    }}>
      <h3 style={{ color: '#0f172a', margin: '0 0 10px 0' }}>
        Enterprise Remote Widget (v1.0.0)
      </h3>
      <p style={{ color: '#475569' }}>
        Modul ini dimuat secara dinamis dari Remote Application.
      </p>
      <button 
        onClick={() => setCount(count + 1)}
        style={{
          backgroundColor: '#2563eb',
          color: '#ffffff',
          border: 'none',
          padding: '8px 16px',
          borderRadius: '4px',
          cursor: 'pointer'
        }}
      >
        Interaksi Lokal: {count}
      </button>
    </div>
  );
}

Buat file entry point remote-app/src/index.js dan remote-app/src/bootstrap.js (praktik terbaik Webpack 5 untuk menangani async boundary shared dependencies):

// remote-app/src/index.js
import('./bootstrap');
// remote-app/src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import Widget from './Widget';

// Render mandiri untuk mode development lokal remote app
const container = document.getElementById('root');
if (container) {
  const root = createRoot(container);
  root.render(<Widget />);
}

Buat file HTML dasar remote-app/public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Remote App Standalone</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

Sekarang, buat file konfigurasi Webpack untuk Remote di remote-app/webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3001/',
    clean: true,
  },
  devServer: {
    port: 3001,
    historyApiFallback: true,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, OPTIONS',
      'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Widget': './src/Widget',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

Langkah 3: Konfigurasi Host Application (host-app)

Pindah ke direktori host-app, inisialisasi, dan instal dependensi:

cd ../host-app
npm init -y
npm install react@18.2.0 react-dom@18.2.0
npm install -D webpack@5.89.0 webpack-cli@5.1.4 webpack-dev-server@4.15.1 html-webpack-plugin@5.6.0 babel-loader@9.1.3 @babel/core@7.23.9 @babel/preset-react@7.23.3

Buat file konfigurasi Babel host-app/babel.config.json:

{
  "presets": [
    "@babel/preset-react"
  ]
}

Buat komponen Error Boundary untuk menangani kegagalan lintas remote app di host-app/src/ErrorBoundary.jsx:

import React, { Component } from 'react';

export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }

  componentDidCatch(error, errorInfo) {
    console.error("Micro-Frontend Error Captured:", error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        <div style={{
          padding: '20px',
          backgroundColor: '#fef2f2',
          border: '1px solid #fecaca',
          borderRadius: '8px',
          color: '#991b1b'
        }}>
          <h4>Gagal Memuat Modul Remote</h4>
          <p>Modul eksternal sedang mengalami gangguan teknis. Sistem tetap berjalan normal.</p>
        </div>
      );
    }

    return this.props.children;
  }
}

Buat komponen utama Host di host-app/src/App.jsx yang memuat Remote Widget menggunakan React.lazy dan dibungkus ErrorBoundary:

import React, { Suspense, lazy } from 'react';
import ErrorBoundary from './ErrorBoundary';

// Deklarasi Remote Module menggunakan format: remoteName/exposedModuleName
const RemoteWidget = lazy(() => import('remoteApp/Widget'));

export default function App() {
  return (
    <div style={{
      padding: '40px',
      fontFamily: 'sans-serif',
      maxWidth: '800px',
      margin: '0 auto'
    }}>
      <header style={{ borderBottom: '2px solid #e2e8f0', paddingBottom: '20px', marginBottom: '20px' }}>
        <h1 style={{ color: '#1e293b', margin: 0 }}>Enterprise Host Shell</h1>
        <p style={{ color: '#64748b' }}>Aplikasi Kontainer Utama yang mengintegrasikan Micro-Frontends.</p>
      </header>

      <main>
        <section>
          <h2>Dashboard Utama Host</h2>
          <p>Bagian ini dikelola sepenuhnya oleh tim Core Infrastructure Host.</p>
        </section>

        <section style={{ marginTop: '30px' }}>
          <h3>Modul Eksternal (Remote App Integration)</h3>
          <ErrorBoundary>
            <Suspense fallback={<div style={{ padding: '20px', color: '#64748b' }}>Memuat modul remote secara asinkron...</div>}>
              <RemoteWidget />
            </Suspense>
          </ErrorBoundary>
        </section>
      </main>
    </div>
  );
}

Buat file entry point host-app/src/index.js dan host-app/src/bootstrap.js:

// host-app/src/index.js
import('./bootstrap');
// host-app/src/bootstrap.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);

Buat file HTML dasar host-app/public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Enterprise Host Shell</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>

Buat file konfigurasi Webpack untuk Host di host-app/webpack.config.js:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  entry: './src/index.js',
  output: {
    publicPath: 'http://localhost:3000/',
    clean: true,
  },
  devServer: {
    port: 3000,
    historyApiFallback: true,
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.2.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.2.0' },
      },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

6. Bedah Kode & Penjelasan Detail Baris Kunci

Mari kita analisis parameter paling krusial di dalam ModuleFederationPlugin:

  1. name: 'hostApp' / name: 'remoteApp': Menetapkan identitas unik global untuk setiap kontainer aplikasi. Nama ini digunakan oleh Webpack untuk mendaftarkan variabel pada objek global jendela browser (window).

  2. filename: 'remoteEntry.js': Hanya didefinisikan pada Remote App. File manifes ini adalah titik masuk utama yang diunduh oleh Host untuk mengetahui fungsi, komponen, dan dependensi apa saja yang diekspor oleh Remote.

  3. exposes vs remotes:

    • exposes pada Remote App mendefinisikan alias path internal ('./Widget') yang boleh diakses oleh aplikasi luar.
    • remotes pada Host App mendefinisikan lokasi URL di mana file remoteEntry.js milik Remote App dapat ditemukan (remoteApp@http://localhost:3001/remoteEntry.js).
  4. shared: { react: { singleton: true, requiredVersion: '^18.2.0' } }: Parameter ini adalah jantung dari stabilitas React Micro-Frontend. Tanpa pengaturan singleton: true, Host dan Remote dapat memuat instansiasi pustaka React mereka sendiri secara terpisah, yang berakibat fatal pada Hooks failure (Invalid hook call error karena adanya duplikasi objek React internal).


7. Skenario Produksi & Studi Kasus Dunia Nyata

Di lingkungan produksi enterprise (misalnya platform e-commerce skala besar atau banking super app), arsitektur ini diterapkan dengan topologi infrastruktur sebagai berikut:

  • Pemisahan Domain & CDN: Host dideploy ke domain utama (https://app.enterprise.com), sementara Remote App (misalnya modul keranjang belanja, modul profil pengguna, dan modul analitik) dideploy ke sub-domain terisolasi (https://cart.enterprise.com, https://profile.enterprise.com).
  • Cloudflare Edge Caching: File remoteEntry.js di-cache di Cloudflare CDN dengan Cache-Control no-cache atau masa TTL sangat pendek (misalnya 60 detik) agar pembaruan kode (hot-fix) dari Remote App dapat langsung dirasakan pengguna tanpa harus merilis ulang Host Application.
  • Nginx Reverse Proxy Configuration:
server {
    listen 443 ssl http2;
    server_name cart.enterprise.com;

    ssl_certificate /etc/letsencrypt/live/cart.enterprise.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/cart.enterprise.com/privkey.pem;

    location / {
        root /var/www/remote-cart/dist;
        try_files $uri $uri/ /index.html;
        
        # Konfigurasi CORS krusial untuk Module Federation
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type' always;
    }
}

8. Common Pitfalls, Anti-Patterns & Solusi

  1. Anti-Pattern: Berbagi Seluruh node_modules Secara Brutal

    • Masalah: Mendefinisikan shared: dependencies di package.json secara otomatis tanpa kurasi. Ini menyebabkan version mismatch dan ukuran file bundle yang membengkak karena mengunduh pustaka yang tidak perlu.
    • Solusi: Hanya bagikan dependensi inti yang benar-benar membutuhkan status singleton (seperti react, react-dom, dan pustaka state manajemen global).
  2. Pitfall: CORS Policy Block pada remoteEntry.js

    • Masalah: Browser menolak memuat remoteEntry.js karena Host dan Remote berada di origin yang berbeda dan server web Remote tidak menyertakan header Access-Control-Allow-Origin: *.
    • Solusi: Pastikan web server (Nginx/Caddy/Apache) menyertakan header CORS pada respon file manifes.
  3. Pitfall: Lupa Membungkus Remote Component dengan ErrorBoundary

    • Masalah: Ketika server Remote down atau jaringan terputus, seluruh aplikasi Host ikut crash menampilkan layar putih.
    • Solusi: Selalu kombinasikan React.lazy / Suspense dengan kustom ErrorBoundary yang kokoh.

9. Benchmark, Metrik Kinerja & Tuning Performa

Untuk mengukur efisiensi sistem Module Federation di produksi, gunakan alat pengujian performa seperti Lighthouse CI dan k6 untuk mensimulasikan lonjakan lalu lintas ribuan pengguna secara bersamaan.

Metrik Utama yang Dipantau:

  • FCP (First Contentful Paint): Harus berada di bawah 1.2 detik karena Host shell memuat kerangka dasar dengan sangat cepat.
  • TBT (Total Blocking Time): Pastikan eksekusi skrip dinamis tidak memblokir Main Thread.
  • Ukuran remoteEntry.js: File manifes harus dijaga ukurannya agar tidak melebihi 5KB agar proses handshake jaringan terjadi secara instan.

[!TIP] Gunakan strategi pre-fetching pada tautan menu navigasi agar file remoteEntry.js dari Remote App diunduh sebelum pengguna benar-benar mengklik menu tersebut, menciptakan pengalaman navigasi yang terasa seperti native app tanpa jeda sama sekali.


10. Checklist Penerapan Produksi & Kesimpulan

Sebelum melakukan promosi sistem ke lingkungan Production (Live Environment), verifikasi kesiapan sistem dengan checklist operasional berikut:

  • Konfigurasi CORS telah diaktifkan secara benar pada server/CDN yang melayani Remote App.
  • Semua dependensi bersama (shared dependencies) dikunci menggunakan rentang semver yang kompatibel (misalnya ^18.2.0).
  • Error Boundary terpasang pada setiap titik integrasi modul eksternal.
  • Mekanisme Cache Busting atau versi unik pada URL remoteEntry diterapkan untuk menghindari masalah browser caching yang kedaluwarsa.
  • Uji beban (load testing) menggunakan k6 telah dijalankan pada endpoint CDN remote app.

Dengan menerapkan arsitektur Micro-Frontends melalui Module Federation secara disiplin, organisasi enterprise dapat mencapai otonomi tim yang maksimal, meningkatkan kecepatan rilis secara drastis, serta mempertahankan stabilitas performa aplikasi web modern skala masif.

🏷️ Tags: #Webpack #Module Federation #Micro-Frontends #React #Enterprise Architecture