Yasin Ateş
5 dk Medium

Micro Frontend Mimarisi (React Örnekleriyle)

Micro Frontend Mimarisi (React Örnekleriyle)
Monolit Mimariden Micro Frontend’e

Bir ürün büyüdükçe onu geliştiren organizasyon da büyür. Farklı iş alanları ortaya çıkar, ekipler bölünür ve her ekibin kendi hızı, kendi öncel ikleri oluşur. Ama codebase hala tek parça kalmaya devam ediyorsa, her küçük değişiklik için koordinasyon ve her deployment süreçleri tüm organizasyonu etkiler.

İşte bu noktada Micro Frontend devreye giriyor.

Bu yazıda micro frontend mimarisini sıfırdan anlatmaya çalışacağım. Hazır kütüphane listesi vermek yerine işin altında yatan mantığı açıklamak istedim. Örnekleri React ile yazdım ama anlattığım mimari prensipler (izolasyon, event bus, entegrasyon yaklaşımları) herhangi bir framework ile uygulanabilir. Ben makaleyi yazarken “tercihen” tüm yapıyı React üzerinden göstermek istedim.

Bu konuya özel bir ilgim var. Daha önce çalıştığım şirketlerden birinde geliştirilen açık kaynaklı (VoltranJS) micro frontend framework projesinde maintainer olarak bulunma şansım oldu; bu deneyim, micro frontend’i hem teoride hem pratikte derinlemesine anlamamı sağladı.

1. Micro Frontend Nedir? 🤔

Micro Frontend, büyük ve karmaşık bir frontend uygulamasını; birbirinden bağımsız geliştirilebilen, bağımsız deploy edilebilen ve birbirinin çalışmasını engellemeden güncellenebilen küçük parçalara bölme mimarisidir. Bu bağımsız parçaların her birine micro app denir.

🎯Kısaca: Microservice’in backend’e yaptığını, Micro Frontend ile frontend’de yapıyoruz.
Ancak backend microservice’lerinden farklı olarak, micro app’ler aynı tarayıcı ortamını paylaşır (aynı DOM, aynı window, aynı localStorage). Bu yüzden izolasyon doğal olarak gelmez; bilinçli olarak sağlanmalıdır.
Monolit ve Micrp Frontend CI / CD

Şimdi örnek olarak, bir e-ticaret sitesini düşünelim. Anasayfa, ürün listesi, ürün detay, sepet, ödeme; bunların hepsi aslında farklı iş mantıklarına, farklı gereksinimlere ve farklı değişim hızlarına sahip alanlar. Micro frontend bu alanların her birinin:

★ Kendi repository’sinde olmasını

★ Kendi CI/CD pipeline’ına sahip olmasını

★ Bağımsız olarak deploy edilmesini

★ İstenirse farklı teknoloji yığınları kullanmasını

mümkün kılar.

⚠️ Farklı framework’leri kullanma özgürlüğü teoriktir. Pratikte çoğu başarılı micro frontend uygulaması tek bir ana framework etrafında standardize olur. Bu konuyu Bölüm 3 - Dezavantajlar bölümünde detaylı ele aldım.

2. Monolith vs Micro Frontend 🆚

1┌─────────────────────────────────────────────┐
2Tek Büyük Frontend App3│  ┌────────┐  ┌─────────┐  ┌──────────────┐  │
4│  │ Header │  │ Ürünler │  │    Ödeme     │  │
5│  └────────┘  └─────────┘  └──────────────┘  │
6Tek RepoTek DeployTüm Ekipler7└─────────────────────────────────────────────┘

Monolitik yapıda büyüdükçe yaşanan sorunlar şunlardır:

★ Her küçük değişiklik için tüm uygulama deploy edilmek zorunda

★ Birden fazla ekip aynı codebase üzerinde çalıştığı için sürekli merge conflict’ler oluyor

★ Farklı ekiplerin farklı hız ihtiyaçları tek pipeline’a sıkışıyor

★ Legacy kod modernize edilmek istendiğinde ya hepsi ya hiçbiri kararı verilmek zorunda kalınıyor

★ Bir ekibin hatası tüm uygulamayı etkiliyor

Micro frontend bu sorunları şu şekilde çözer:

1┌──────────────┐  ┌───────────────┐  ┌────────────┐
2Header    │  │    Ürünler    │  │    Sepet3Ekip A    │  │    Ekip B     │  │   Ekip C4Bağımsız    │  │  Bağımsız     │  │ Bağımsız5Deploy     │  │   Deploy      │  │  Deploy6└──────────────┘  └───────────────┘  └────────────┘
7        │                │                  │
8        └────────────────┴──────────────────┘
910               ┌──────────────────┐
11Main App12               └──────────────────┘

3. Avantajlar ve Dezavantajlar ⚖️

Micro Frontend Avantajları / Dezavantajları

3.1 Avantajlar

Bağımsız Deployment

Her ekip kendi micro app’ini diğer ekipleri beklemeden canlıya alabilir. Örneğin, ödeme ekibinin deploy’u sepet ekibinin release takvimini etkilemez.

Takım Otonomisi

Her ekip kendi teknoloji kararlarını verebilir. Onboarding kolaylaşır; yeni geliştirici tüm uygulamayı değil, sadece kendi micro app’ini öğrenmek zorunda kalır.

Hata İzolasyonu

Bir micro app hata verse error boundary sayesinde diğerleri çalışmaya devam eder. Monolitte tek bir bug tüm sayfayı çökertebilir.

Kademeli Modernizasyon

Legacy bir uygulamayı tek seferde yeniden yazmak yerine parça parça modern teknolojiye taşımak mümkün. Eski sayfalar çalışırken yeni micro app’ler birer birer eklenir.

Paralel Geliştirme

Birden fazla ekip aynı anda farklı micro app’ler üzerinde çalışabilir, birbirini bloke etmeden sprint’lerini tamamlar.

A/B Testi Kolaylığı

Sadece ilgili micro app’in farklı versiyonunu deploy ederek bölgesel veya kullanıcı bazlı test yapılabilir.

3.2 Dezavantajlar

Artan Operasyonel Karmaşıklık

Her micro app ayrı bir servis demektir; ayrı repo, ayrı CI/CD, ayrı monitoring. Küçük ekipler için bu yük ağır olabilir.

İlk Yükleme Performansı

Her micro app ayrı JavaScript bundle’ı anlamına gelir. HTTP/2 multiplexing (tek bağlantı üzerinden paralel istek gönderme) ile çoklu istek sorunu azalsa da, toplam JavaScript parse ve execution süresi artar. Doğru yapılandırılmamış bağımlılık stratejisi sayfayı şişirebilir.

Tasarım Tutarsızlığı

Her ekip kendi CSS’ini yönetirse zaman içinde görsel tutarsızlıklar oluşur. Ortak design system zorunlu hale gelir.

Test Karmaşıklığı

Entegrasyon ve e2e testler birden fazla servisi aynı anda ayakta tutmayı gerektirir.

Bağımlılık Çatışmaları

Farklı micro app’ler uyumsuz kütüphane versiyonları kullanırsa tarayıcıya aynı kütüphanenin iki instance’ı yüklenir.

Çoklu Framework Maliyeti

Her ekibin kendi framework’ünü kullanabilmesi teorik bir özgürlüktür. Pratikte her ek teknoloji yığınının runtime’ı ayrı yüklenir (framework core ~40–50kb + router ~15–20kb + state kütüphanesi ~10–30kb gzipped), geliştiriciler ekipler arası geçiş yapamaz, UI kit her framework için ayrı yazılır ve test matrisi katlanır. Çoğu başarılı uygulama tek bir ana framework etrafında standardize olur.

Yanlış Sınır Çizmek

Domain sınırları iş gereksinimlerine göre değil teknoloji tercihlerine göre çizilirse gereksiz network istekleri ve veri fazlalığı kaçınılmaz olur. En yaygın yapısal hata budur.

4. Temel Kavramlar 🧱

4.1 Main App

Micro frontend mimarisinin çatısıdır. Diğer tüm micro app’leri bir araya getiren ana yapıdır. Routing, navigation ve paylaşılan layout bu katmanda yaşar. Authentication ve global state de genellikle burada yönetilir. Main app aynı zamanda micro app’lerin nasıl ve nerede birleştirileceğine karar veren orkestrasyon görevini de üstlenir, bu birleştirme client-side, server-side veya edge-side olabilir.

Bazı Micro Frontend kaynaklarında “shell” veya “container” olarak da adlandırılır.

💡“shell” ve “container” kelimeleri yazılım dünyasında çok kullanılan kelimeler olduğu için makalede, karışmaması adına ben “Main App” olarak kullandım. Sadece kod örneklerinde “shell” kullandım

4.2 Micro App

Belirli bir iş alanından sorumlu, bağımsız olarak deploy edilen küçük frontend uygulamasıdır. Module Federation terminolojisinde remote olarak da adlandırılır.

💡“remote” kelimesi de yine aynı şekilde yazılım dünyasında çok kullanılan bir ifade olduğu için ben makale içerisinde Module Federation kısmı hariç “Micro App” olararak kullandım

4.3 Routing

Main app, route değiştiğinde mevcut micro app’i unmount edip yeni micro app’i mount etmekten sorumludur. İki yaygın pattern vardır:

Main app tarafında routing: Tüm URL navigasyonu main app’te tanımlıdır. Micro app sadece mount noktasını bilir; hangi URL’de gösterileceğinden habersizdir. Merkezi kontrol sağlar, micro app’ler tamamen izole kalır.

1// shell/src/App.js - main app (kod dizinlerinde bazı projelerde "shell" ismi kullanılır)
2import { BrowserRouter, Routes, Route } from 'react-router-dom';
3import { Suspense, lazy } from 'react';
4
5// Micro app'ler lazy yüklenir (entegrasyon detayları Bölüm 7'de)
6const Home    = lazy(() => import('homeApp/Home'));
7const Catalog = lazy(() => import('catalogApp/Catalog'));
8const Cart    = lazy(() => import('cartApp/Cart'));
9
10const Fallback = ({ name }) => (
11  <div className="mfe-skeleton">{name} yükleniyor...</div>
12);
13
14export default function App() {
15  return (
16    <BrowserRouter>
17      <Routes>
18        <Route path="/" element={
19          <Suspense fallback={<Fallback name="Ana Sayfa" />}>
20            <Home />
21          </Suspense>
22        } />
23        <Route path="/products/*" element={
24          <Suspense fallback={<Fallback name="Ürünler" />}>
25            <Catalog />
26          </Suspense>
27        } />
28        <Route path="/cart" element={
29          <Suspense fallback={<Fallback name="Sepet" />}>
30            <Cart />
31          </Suspense>
32        } />
33      </Routes>
34    </BrowserRouter>
35  );
36}

Micro app içi routing: Micro app kendi içindeki navigasyonu kendisi yönetir (örneğin ürün listesinden ürün detayına geçiş). Main app sadece micro app’i hangi root URL’e mount edeceğini bilir, içi micro app’in sorumluluğundadır.

1// catalog-micro-app/src/CatalogApp.js
2import { Routes, Route } from 'react-router-dom';
3import ProductList from './ProductList';
4import ProductDetail from './ProductDetail';
5
6// Main app bu micro app'i "/products/*" altında mount eder.
7// Micro app kendi alt route'larını yönetir.
8export default function CatalogApp() {
9  return (
10    <Routes>
11      <Route index element={<ProductList />} />
12      <Route path=":id" element={<ProductDetail />} />
13    </Routes>
14  );
15}
💡 İkisi birbirini dışlamaz; main app üst düzey (hangi micro app gösterilecek) routing’i yaparken, micro app kendi alt route’larını yönetir. Birlikte çalıştığında akış şu şekilde olur:
1URL: /products/abc-123
2
3Main app routing (App.js):
4  "/products/*"<Catalog /> micro app'ini mount et
5
6Micro app içi routing (CatalogApp.js):
7  "/products/"<ProductList />     (index route)
8  "/products/:id"<ProductDetail />   ← abc-123 buraya düşer
Main app’teki "/products/*" wildcard'ı, micro app'in kendi alt yollarını tanımasını mümkün kılar. Main app "hangi micro app" sorusunu, micro app ise "hangi alt sayfa" sorusunu yanıtlar.

5. İzolasyon Nasıl Sağlanır? 🔒

Micro frontend’de en kritik sorunlardan biri izolasyon: bir micro app’in CSS’i veya JavaScript’i diğerini nasıl ezmez?

Micro Frontend İzolasyon

5.1 CSS İzolasyonu

Tarayıcıda global CSS scope’u tüm micro app’ler paylaşır. Aynı class ismi iki farklı micro app’ta kullanılıyorsa birbirini ezer.

1.container { background: blue; }  /* Micro App A */
2.container { background: red; }   /* Micro App B -> üsteki kuralı eziyor! */

Bu sorunu çözmenin en pratik ve yaygın yolu CSS Modules kullanmaktır. CSS Modules’de dosya adına .module.css uzantısı verilir ve derleme sırasında her class adına otomatik hash eklenir. Böylece .container yerine .ProductList_container__3xMq2 gibi eşsiz bir isim üretilir; çakışma imkansız hale gelir. SCSS Modules aynı izolasyon mekanizmasını kullanır (.module.scss uzantısı); üstüne değişken, nesting ve mixin gibi SCSS özelliklerini ekler. Bu yazıdaki örneklerde SCSS Modules tercih ettim.

1/* ProductList.module.scss */
2.container {
3  padding: 1rem;
4  border-radius: 8px;
5
6  .title {
7    font-size: 1.5rem;
8    color: #111827;
9  }
10
11  &:hover { background: #f9fafb; }
12
13  $primary: #4f46e5;
14
15  .badge {
16    background: $primary;
17    color: white;
18    padding: 2px 8px;
19    border-radius: 999px;
20  }
21}
1// ProductList.js
2import styles from './ProductList.module.scss';
3
4function ProductList() {
5  return (
6    <div className={styles.container}>
7      <h2 className={styles.title}>Ürünler</h2>
8      <span className={styles.badge}>Yeni</span>
9    </div>
10  );
11}
12
13// DOM'a yansıyan: <div class="ProductList_container__3xMq2">
💡 Alternatif yaklaşımlar: CSS-in-JS kütüphaneleri (styled-components, Emotion) veya Shadow DOM da izolasyon sağlar. Ancak micro frontend bağlamında SCSS Modules en düşük runtime maliyetini ve en geniş araç uyumluluğunu sunduğu için tercih edilir.

5.2 JavaScript İzolasyonu

Global değişkenler, window üzerindeki property'ler ve localStorage en sık çakışma kaynaklarıdır.

1// Micro App A
2window.currentUser = { id: 1, name: 'Merve' };
3
4// Micro App B, Micro App A'nın datasını eziyor
5window.currentUser = { id: 2, name: 'Rabia' };
Olması gereken
1window.__MFE__ = window.__MFE__ || {};
2
3window.__MFE__.header = { currentUser: { id: 1, name: 'Rabia' }, version: '1.2.3' };
4window.__MFE__.cart   = { items: [], total: 0 };

★ localStorage için prefix;

1const PREFIX = 'mfe_header_';
2localStorage.setItem(`${PREFIX}user`, JSON.stringify(user));
3localStorage.getItem(`${PREFIX}user`);
⚠️ Namespace yalnızca bir naming convention’dur; runtime’da enforce edilmez. Herhangi bir micro app window.__MFE__.cart diye başka micro app'in verisini okuyabilir veya ezebilir. Bu yaklaşımın işe yaraması ekiplerin kurala uyma disiplinine bağlıdır.

6. Event Bus ile İletişim 📡

Micro app’ler birbirini doğrudan import etmemeli; bu bağımlılık oluşturur ve bağımsız deployment’ı bozar. İletişim event tabanlı olmalı. window üzerindeki CustomEvent API'si bu iş için yeterlidir:

1const bus = {
2  emit(event, data) {
3    window.dispatchEvent(new CustomEvent(event, { detail: data }));
4  },
5
6  on(event, handler) {
7    /* wrapper: 
8      ★ (1) e.detail'i çıkarır, 
9      ★ (2) referansı cleanup için saklanır
10    */
11    const wrapper = (e) => handler(e.detail);
12
13    window.addEventListener(event, wrapper);
14    return () => window.removeEventListener(event, wrapper); // cleanup
15  },
16};
17
18export { bus };

Bu paketi @mfe/event-bus adıyla private npm registry'nizde (GitHub Packages, npm org scope, Verdaccio vb.) yayınlarsınız. Paketin micro app'lere nasıl dağıtılacağı (Module Federation shared config, vendor CDN vb.) kullandığınız entegrasyon yaklaşımına bağlıdır; bu yaklaşımları Bölüm 7'de detaylı ele alacağım.

Neden CustomEvent?

window üzerinde çalıştığı için farklı bundle'lardan emit edilen event'lar aynı sayfada birbirini doğal olarak görür. Ekstra instance paylaşımı derdine gerek kalmaz, her micro app kendi bundle'ında bus.emit() çağırdığında diğer micro app'lerin bus.on() listener'ları bunu yakalar. Sıfır bağımlılık, tarayıcı native API'si.

6.1 Kullanım

💡 Naming convention: mfe:{microapp}:{action} formatı önerilir. mfe: prefix'i çakışmayı önler, genelde buradaki ön ek ilgili microfrontend framework’ünün ismidir, ortadaki segment kaynağı, sondaki segment eylemi belirtir.
1// ProductsApp.jsx
2import React from "react";
3import { bus } from "@org/event-bus";
4
5const products = [
6  { id: 1, name: "Keyboard", price: 500 },
7  { id: 2, name: "Mouse", price: 300 },
8];
9
10export default function ProductsApp() {
11  const addToCart = (product) => {
12
13    // ★ Ürün bilgisini gönderiyoruz
14    bus.emit("mfe:cart:add", {
15      productId: product.id,
16      price: product.price,
17      quantity: 1,
18    });
19  };
20
21  return (
22    <div>
23      <h2>Products</h2>
24      {products.map((product) => (
25        <div key={product.id}>
26          <span>
27            {product.name} - {product.price}28          </span>
29          <button onClick={() => addToCart(product)}>
30            Add to Cart
31          </button>
32        </div>
33      ))}
34    </div>
35  );
36}
1// CartApp.jsx
2import React, { useEffect, useState } from "react";
3import { bus } from "@org/event-bus";
4
5export default function CartApp() {
6  const [cartItems, setCartItems] = useState([]);
7
8  useEffect(() => {
9    const handleAddToCart = (item) => {
10      setCartItems((prev) => {
11        const existing = prev.find(p => p.productId === item.productId);
12
13        if (existing) {
14          return prev.map(p =>
15            p.productId === item.productId
16              ? { ...p, quantity: p.quantity + 1 }
17              : p
18          );
19        }
20
21        return [...prev, item];
22      });
23    };
24
25    // ★ Diğer componentten gönderdiğimiz ürün bilgisini alıyoruz ve cleanup çalıştırıyoruz
26
27    /* Bu sayede;
28      ✔ Component unmount olduğunda listener temizlenir
29      ✔ Memory leak olmaz
30      ✔ Multiple mount/unmount sorun çıkarmaz
31   */
32    const unsubscribe = bus.on("mfe:cart:add", handleAddToCart);
33    return unsubscribe;
34  }, []);
35
36  return (
37    <div>
38      <h2>Cart</h2>
39      {cartItems.map((item) => (
40        <div key={item.productId}>
41          Product: {item.productId} | 
42          Quantity: {item.quantity} | 
43          Price: {item.price}
44        </div>
45      ))}
46    </div>
47  );
48}

6.2 İletişim Senaryoları

Micro Frontend Componentler Arası İletişim

Client → Client: Aynı sayfadaki iki micro app konuşuyor

1import { bus } from '@org/event-bus';
2
3function ProductCard({ product }) {
4  return (
5    <button onClick={() =>
6      bus.emit('mfe:cart:add', { productId: product.id, price: product.price })
7    }>
8      Sepete Ekle
9    </button>
10  );
11}

Client → Server → Bildirim: Backend eylemi sonrası diğer micro app’lere bildirim

Sunucuya istek göndermek için event bus gerekmez; standart fetch yeterlidir. Ancak sonucun diğer micro app'lere bildirilmesi gerekiyorsa event bus devreye girer:

1// checkout micro app
2import { bus } from '@org/event-bus';
3
4async function initiatePayment(cartId, paymentMethod) {
5  // Sunucuya standart fetch ile istek
6  const res = await fetch('/api/checkout/initiate', {
7    method: 'POST',
8    headers: { 'Content-Type': 'application/json' },
9    body: JSON.stringify({ cartId, paymentMethod }),
10  });
11  const { orderId } = await res.json();
12
13  // Sonucu diğer micro app'lere event bus ile bildir
14  bus.emit('mfe:order:created', { orderId });
15}

7. Entegrasyon Yaklaşımları 🔗

Micro app’leri bir araya getirmenin birden fazla yolu vardır. Her birinin güçlü ve zayıf yanları farklıdır; projenin ihtiyaçlarına göre doğru yaklaşımı seçmek önemlidir.

7.1 Build-Time Entegrasyon

Micro app’ler npm paketi olarak yayınlanır, main app build aşamasında tek bundle’a dahil eder. Burada npm paketi olarak yayınlanan şey micro app’in kendisidir. İş mantığı, state, API çağrıları dahil tüm micro app main app’in build’ine gömülür.

1Micro App A → npm publish → @org/header@1.2.3
2Main App → npm install @org/header → build → deploy

★ Kullanımı basit

★ Main app’i rebuild etmeden micro app güncellenemiyor, gerçek bağımsız deployment sağlanamıyor

7.2 Run-Time Client-Side Entegrasyon

Micro app’ler tarayıcıda çalışma zamanında dinamik olarak yüklenir. İki şekilde uygulanabilir: manuel yaklaşım ve Module Federation.

7.2.1 Manuel Yaklaşım

En basit haliyle her micro app’in bundle’ı CDN’den çekilir ve sayfaya mount edilir:

1async function bootstrapApp() {
2  const [HeaderModule, ProductsModule] = await Promise.all([
3    import('https://header.cdn.example.com/header.esm.js'),
4    import('https://products.cdn.example.com/products.esm.js'),
5  ]);
6
7  HeaderModule.default.mount(document.getElementById('header'));
8  ProductsModule.default.mount(document.getElementById('products'));
9}

★ Her micro app tamamen bağımsız deploy edilebilir

★ İlk yüklenme süresi artar

★ Bağımlılık paylaşımı (React gibi ortak kütüphanelerin tekrar yüklenmemesi) geliştirici tarafından manuel yönetilmelidir

Bu manuel yönetim sorunu bizi ortak bağımlılık problemine ve Module Federation’a getiriyor.

7.2.2 Ortak Bağımlılık Problemi

Her micro app React ve benzeri kütüphaneleri kendi bundle’ına dahil ederse, kullanıcı aynı kodu defalarca indirir. Bu durum hem bant genişliği israfı hem de aynı kütüphanenin iki ayrı instance’ının sayfada aynı anda çalışması sorununu doğurur.

1header.bundle.jsReact (~42kb gzipped) + kendi kodu (8kb)
2products.bundle.jsReact (~42kb gzipped) + kendi kodu (15kb)
3cart.bundle.jsReact (~42kb gzipped) + kendi kodu (6kb)
4──────────────────────────────────────────────────────────────
5Toplam 3 kopya yükleniyor, bunların 2'si gereksiz → 84kb israf

Vendor Bundle Yaklaşımı (Module Federation Öncesi)

Module Federation öncesinde yaygın çözüm, ortak kütüphaneleri tek bir vendor bundle (vendor.js) olarak CDN'den sunmak ve micro app'lerin webpack externals ayarıyla bu kütüphaneleri kendi bundle'larına dahil etmemesiydi. externals ayarı, bir kütüphanenin bundle'a dahil edilmemesini ve runtime'da global scope'tan (örneğin window.React) alınmasını sağlar:

1// Micro app webpack.config.js (Module Federation öncesi yaklaşım)
2module.exports = {
3  externals: {
4    react: 'React',           // import React → window.React'tan alınır
5    'react-dom': 'ReactDOM',  // import ReactDOM → window.ReactDOM'dan alınır
6  },
7};
1<!-- Main app layout'u - vendor kütüphaneleri global scope'a yükler -->
2<script src="https://cdn.example.com/vendor/react.18.2.0.min.js"></script>
3<script src="https://cdn.example.com/vendor/react-dom.18.2.0.min.js"></script>
4
5<!-- Micro app bundle'ları artık React içermez, window.React'ı kullanır -->
6<script src="https://header.cdn.example.com/header.bundle.js"></script>
7<script src="https://products.cdn.example.com/products.bundle.js"></script>

Bu yaklaşım işe yarar ama yönetimi manueldir; hangi kütüphanenin hangi versiyonda olacağını merkezi olarak koordine etmek gerekir. Module Federation bu süreci otomatize eder.

7.2.3 Module Federation

Module Federation

Webpack 5 ile gelen Module Federation, micro frontend dünyasında oyunu değiştiren en önemli yenilik oldu. Bağımlılık paylaşımını runtime’da, webpack seviyesinde çözer.

Main App (Host): Diğer uygulamaların modüllerini tüketen ana uygulama. Module Federation terminolojisinde “host” olarak adlandırılır.

Remote (Micro App): Kendi modüllerini dışarıya açan bağımsız uygulama. Her remote bir remoteEntry.js dosyası üretir. Bu dosya remote'un hangi modülleri dışarıya açtığını ve bu modüllerin JavaScript dosyalarının nerede bulunduğunu bildiren bir manifest gibi çalışır. Main app sayfa yüklendiğinde önce bu manifest'i indirir, ardından ihtiyaç duyulan modülün gerçek kodunu talep eder.

Shared: Ortak bağımlılıklar. singleton: true ile işaretlendiğinde React gibi kütüphanelerden yalnızca tek bir instance yüklenmesi garanti edilir.

Remote (Micro App) Konfigürasyonu:

1// products-app/webpack.config.js (micro app)
2const { ModuleFederationPlugin } = require('webpack').container;
3
4module.exports = {
5  output: {
6    publicPath: 'https://products.cdn.example.com/',
7  },
8  plugins: [
9    new ModuleFederationPlugin({
10      name: 'productsApp',
11      filename: 'remoteEntry.js',
12      exposes: {
13        './ProductList':   './src/components/ProductList',
14        './ProductDetail': './src/components/ProductDetail',
15      },
16      shared: {
17        react:       { singleton: true },
18        'react-dom': { singleton: true },
19      },
20    }),
21  ],
22};

Host (Main App) Konfigürasyonu:

1// shell-app/webpack.config.js (main app)
2const { ModuleFederationPlugin } = require('webpack').container;
3
4module.exports = {
5  plugins: [
6    new ModuleFederationPlugin({
7      name: 'shell',
8      remotes: {
9        productsApp: 'productsApp@https://products.cdn.example.com/remoteEntry.js',
10        cartApp:     'cartApp@https://cart.cdn.example.com/remoteEntry.js',
11        headerApp:   'headerApp@https://header.cdn.example.com/remoteEntry.js',
12      },
13      shared: {
14        react:       { singleton: true },
15        'react-dom': { singleton: true },
16      },
17    }),
18  ],
19};

Host (Main App) ile Remote (Micro App) Modülü Kullanma:

1// shell-app/src/App.js (main app)
2import React, { Suspense, lazy } from 'react';
3
4// Remote modüller lazy import ile yüklenir
5const ProductList = lazy(() => import('productsApp/ProductList'));
6const CartBadge   = lazy(() => import('cartApp/CartBadge'));
7const Header      = lazy(() => import('headerApp/Header'));
8
9const Fallback = ({ name }) => (
10  <div className="mfe-skeleton">{name} yükleniyor...</div>
11);
12
13export default function App() {
14  return (
15    <>
16      <Suspense fallback={<Fallback name="Header" />}>
17        <Header />
18      </Suspense>
19
20      <main>
21        <Suspense fallback={<Fallback name="Ürünler" />}>
22          <ProductList />
23        </Suspense>
24      </main>
25
26      <Suspense fallback={<Fallback name="Sepet" />}>
27        <CartBadge />
28      </Suspense>
29    </>
30  );
31}
Remote modüller lazy ile sarmalandığında yükleme sırasında Suspense fallback'i gösterilir; remote (micro app)'un JavaScript'i indirildikten sonra gerçek bileşen render edilir.
Module Federation 2024 itibarıyla bağımsız bir proje olarak (module-federation/core) webpack dışına çıkmış; farklı bundler’larla da kullanılabilir hale gelmektedir.

7.3 Hangi Yaklaşımı Seçmeli?

Build-time entegrasyon kurulumu en basit olandır ve tip güvenliği doğal olarak gelir, ancak bağımsız deployment sağlayamaz. Main app her güncellemede rebuild edilmek zorundadır. Küçük ekiplerde veya seyrek değişen micro app’lerde işe yarayabilir, ama ölçeklendiğinde darboğaz oluşturur.

Manuel client-side entegrasyon bağımsız deployment sunar; her micro app kendi CDN’inden sunulur ve runtime’da yüklenir. Ancak bağımlılık paylaşımı tamamen geliştiricinin sorumluluğundadır, externals ve CDN ayarlarıyla React gibi kütüphanelerin tekrar yüklenmemesi manuel koordine edilmelidir. İlk yükleme süresi de artar.

Module Federation aynı runtime bağımsızlığını sunarken bağımlılık paylaşımını otomatize eder. singleton: true ile ortak kütüphanelerin tek kopya yüklenmesini garanti eder. Webpack (ve artık diğer bundler'lar) ile entegre çalışır.

8. Design System Yönetimi 🎨

Design System

Birden fazla ekip bağımsız çalıştığında butonlar farklı renklerde, spacing’ler tutarsız, tipografi standartları kaybolmuş olabilir.

Çözüm ise basit, ortak bir design system.

8.1 CSS Değişkenleri (Custom Properties): Tek Kaynak

Tasarım kararlarını (renk, spacing, tipografi, border radius) CSS custom property olarak tanımlamak, tüm micro app’lerde tutarlılık sağlamanın en basit yoludur. Micro app’ler değerleri doğrudan CSS’e yazmak yerine bu değişkenlerden alır:

1/* @org/design-system/variables.css */
2:root {
3  --color-primary:       #4F46E5;
4  --color-primary-hover: #4338CA;
5  --color-success:       #10B981;
6  --color-danger:        #EF4444;
7  --color-text:          #111827;
8  --color-text-muted:    #6B7280;
9  --color-bg:            #FFFFFF;
10  --color-bg-secondary:  #F9FAFB;
11  --color-border:        #E5E7EB;
12
13  --space-xs: 0.25rem;
14  --space-sm: 0.5rem;
15  --space-md: 1rem;
16  --space-lg: 1.5rem;
17  --space-xl: 2rem;
18
19  --font-family:    'Inter', system-ui, sans-serif;
20  --font-size-sm:   0.875rem;
21  --font-size-base: 1rem;
22  --font-size-lg:   1.25rem;
23  --font-size-xl:   1.5rem;
24
25  --radius-sm:   4px;
26  --radius-md:   8px;
27  --radius-lg:   12px;
28  --radius-full: 999px;
29
30  --color-skeleton:      #F0F0F0;
31  --color-skeleton-wave: #E0E0E0;
32}
1/* products-micro-app/ProductCard.module.scss */
2.card {
3  background:    var(--color-bg);
4  border:        1px solid var(--color-border);
5  border-radius: var(--radius-md);
6  padding:       var(--space-md);
7
8  .title { font-size: var(--font-size-lg);  color: var(--color-text); }
9  .price { color: var(--color-primary);     font-size: var(--font-size-xl); }
10}

Bu yaklaşımın avantajı: marka rengi değiştiğinde sadece değişken dosyasını güncellersiniz, tüm micro app’ler otomatik yeni değerleri alır.

8.2 Shared UI Kit: npm Paketi

Değişkenlerin ötesinde, ortak bileşenler (Button, Input, Modal, Card) bir npm paketi olarak yayınlanır. Micro app’ler bu paketi bağımlılık olarak ekler. Burada npm paketi olarak paylaşılan şey yalnızca iş mantığı içermeyen presentational bileşenlerdir; micro app’lerin kendisi bağımsız deploy edilmeye devam eder.

1// products micro app
2import { Button, Card } from '@org/ui-kit';
3
4function ProductCard({ product }) {
5  return (
6    <Card padding="md" bordered>
7      <h3>{product.name}</h3>
8      <Button variant="primary" onClick={() => addToCart(product)}>
9        Sepete Ekle
10      </Button>
11    </Card>
12  );
13}

UI kit semantic versioning ile yönetilir. Minor güncellemelerde (yeni bileşen eklenmesi gibi) micro app’ler kendi hızında güncelleyebilir; major güncellemelerde (breaking change) tüm ekiplere makul bir migration süresi tanınmalıdır.

💡 En iyi sonuç veren yaklaşım: CSS değişkenlerini layout’ta yükle, UI kit’i npm paketi olarak dağıt, major versiyona geçiş için tüm ekiplere 2–3 sprint süre tanı.

9. Ne Zaman Kullanmalı, Ne Zaman Kullanmamalı? 🤓

Micro Frontend Hangi Durumda Kullanılmalı?

Micro frontend’e geçip geçmeyeceğinizi belirleyen asıl soru şu: Ekiplerin iş alanları yeterince ayrışmış mı?

Genel eğilim: Deneyimlerime ve sektördeki yaygın gözlemlere göre micro frontend, birden fazla bağımsız ekip ve belirgin domain ayrışması olduğunda değer üretmeye başlar. Kesin bir sayı olmamakla birlikte, tek ekipli veya çok küçük organizasyonlarda getirdiği operasyonel yük genellikle faydayı aşar.

9.1 Micro Frontend’in Uygun Olduğu Durumlar

★ Farklı iş alanları (checkout, catalog, search, account) arasında belirgin sınırlar var ve bu alanları farklı ekipler yönetiyorsa

★ Ekipler birbirinin deployment’larını bloke etmeye başladıysa

★ Farklı alanların değişim hızları birbirinden çok farklıysa

★ Büyük bir legacy uygulama parça parça modernize edilecekse

★ A/B testleri belirli bir sayfa alanında sık ve bağımsız yapılacaksa

★ Ekipler kendi deploy takvimlerine tam sahip olmak istiyorsa

9.2 Micro Frontend’in Uygun Olmadığı Durumlar

★ Uygulama henüz büyüme aşamasında ve domain sınırları netleşmemişse; yanlış çizilen sınırlar ileride büyük refactoring maliyeti yaratır

★ Ekipler hala her feature için birlikte karar alıyorsa; mimari tek başına organizasyon sorunlarını çözmez

★ Hızlı MVP çıkarmak hedefliyorsa; önce ürün, sonra mimari

★ Performans ve bundle boyutu birincil kısıtsa; her ek micro app overhead getirir

★ Geliştirici deneyimi ve local setup karmaşıklığı tolere edilemiyorsa

Sıradaki Adımlar 🗺️

Bu yazıda micro frontend mimarisinin temel kavramlarını, entegrasyon yaklaşımlarını ve dikkat edilmesi gereken noktaları ele aldım. Buradan sonra araştırmaya devam etmek isterseniz, aklıma gelen bir kaç konu başlığı şu şekilde:

🖥️ Server-Side Entegrasyon ve Hydration

Micro app’lerin sunucu tarafında render edilmesi, layout’un server’da birleştirilmesi ve client’ta interaktif hale getirilmesi (hydration). SSR ve CSR micro app’lerin aynı sayfada birlikte çalışması, graceful degradation stratejileri ve hydration mismatch sorunlarının çözümü.

⚡ Performans ve Monitoring

  • Web Vitals, Micro App Bazında Ölçüm: Her micro app’in Core Web Vitals metriklerinin bağımsız olarak izlenmesi ve raporlanması.
  • Error Boundary: Micro app’lerde hata yönetimi, bir micro app çökerse diğerlerinin etkilenmemesi için izolasyon stratejileri.
  • Skeleton ile CLS Önleme: Micro app yüklenirken layout kaymasını (Cumulative Layout Shift) engellemek için skeleton placeholder kullanımı.
  • Above the Fold / Below the Fold Lazy Loading: Kullanıcının ilk gördüğü alandaki (above the fold) bileşenlerin öncelikli yüklenmesi, sayfa altındaki (below the fold) micro app ve bileşenlerin lazy olarak yüklenmesi.
  • Critical CSS: İlk render için gereken minimum CSS’in inline olarak sunulması, geri kalanın asenkron yüklenmesi.
  • INP - Kullanıcı Etkileşimi Performansı: Interaction to Next Paint metriğinin micro app bazında ölçülmesi ve optimize edilmesi.

🛠️ Local Development Experience

Micro frontend’de günlük geliştirme deneyimi: standalone mode ile micro app’i bağımsız çalıştırma, mock micro app’larla main app entegrasyonunu test etme, Docker Compose ile tam ortam kurulumu. “Kendi micro app’imi geliştirirken 10 servis ayağa kaldırmak zorunda mıyım?” sorusunun yanıtı.

🔺 Next.js ile Micro Frontend

Next.js’in App Router, Server Components ve built-in SSR yetenekleri micro frontend mimarisiyle nasıl birleştirilir? Module Federation’ın Next.js entegrasyonu (@module-federation/nextjs-mf), sayfa bazlı micro app ayrıştırması ve Next.js'in kendi middleware katmanının main app görevi görmesi.

📬 Geri bildirim

Makaleyi yazarken, yazım denetimi için “Claude Opus 4.6 Thinking” modelini, kaynakları belirleme ve araştırma için VoltranJS maintainer’ı olduğum dönemdeki kendi notlarımı ve Chat GPT 5.2'nin derin araştırma özelliğini kullandım. Resimleri üretmek için ise “Gemini 3 Pro Image Preview 2k (Nano Banana Pro)” modelini kullandım.

Yazı ile ilgili tavsiye, öneri, eleştirileri dikkate alıyorum. İletişime geçmek isterseniz bana websitemdeki sosyal medya adreslerimden veya Linkedin üzerinden ulaşabilirsiniz.

Sevgiyle kalın, Yasin 🤗

📚Makaleyi Yazarken Kullandığım Kaynaklar

Açık Kaynak Repolar

Makaleler ve Belgeler

Kitaplar

  • Micro Frontends in Action

https://www.manning.com/books/micro-frontends-in-action

  • Building Micro-Frontends

https://www.buildingmicrofrontends.com/

  • The Art of Micro Frontends

https://www.packtpub.com/product/the-art-of-micro-frontends/9781800563568

  • Practical Module Federation

https://module-federation.myshopify.com/products/practical-module-federation

Kurslar

Podcast’ler