İçeriğe atla

2025-09-04

Micro Frontend Mimarisi Temelleri: Modern Web Uygulamaları İçin Eksiksiz Rehber

Micro frontend mimarisinin temelleri, uygulama stratejileri ve gerçek dünya örnekleri. Module Federation, single-spa ve diğer yaklaşımların detaylı karşılaştırması.

Micro frontend mimarileri, tek sayfalı bir frontend’i bağımsız olarak deploy edilebilen, bağımsız olarak sahiplenilen ve runtime’da birleşen dilimlere böler. Belirli bir dizi sorunu çözerler (ekip büyüklüğü ölçeklendirme, bağımsız sürüm temposu, teknoloji esnekliği) ve buna karşılık yeni bir dizi problem getirirler (runtime kompozisyon karmaşıklığı, paylaşılan bağımlılık yönetimi, dilimler arası state, dilimler arası performans). Benimseme kararı nadiren net bir kazançtır; koordinasyon yükünü sürüm bağımsızlığıyla değiş tokuş eder ve bu takas yalnızca belirli ekip büyüklüklerinde ve ürün yapısı sınırlarında geri kazanır.

Bu yazı, micro frontend mimarisinin temellerini ele alır. Kompozisyon stratejilerini (build-zamanı, sunucu tarafı, Module Federation ile runtime), paylaşılan bağımlılık desenlerini, takası değerli kılan ekip ve ürün yapısı ön koşullarını ve bir monorepo’nun daha ucuza çözeceği organizasyonel nedenlerle micro frontend’lere geçildiğinde ortaya çıkan anti-pattern’leri kapsar.

Kapsamlı Micro Frontend Serisi

Bu, kapsamlı 3 bölümlük serinin 1. Bölümü. İşte tam öğrenme yolunuz:

Micro frontend’lere yeni misiniz? Temel kavramları anlamak için bu yazıyla başlayın, sonra seriyi sırayla takip edin.

Uygulamaya hazır mısınız? Uygulamalı Module Federation örnekleri için Kısım 2’ye geçin.

Production’da çalışıyor musunuz? İleri düzey hata ayıklama ve optimizasyon teknikleri için doğrudan Kısım 3’e gidin.

Micro Frontend’ler Nedir?

Micro frontend’ler, microservis konseptini frontend geliştirmeye genişletir. Tek bir monolitik frontend uygulaması yerine, birden çok küçük, bağımsız olarak deploy edilebilir frontend uygulamasını uyumlu bir kullanıcı deneyimi haline getirirsiniz.

Ana prensipler şunlardır:

  • Teknoloji Agnostik: Takımlar kendi framework’leri ve araçlarını seçebilir
  • Bağımsız Deployment: Her micro frontend bağımsız olarak deploy edilebilir
  • Takım Özerkliği: Farklı takımlar uygulamanın farklı kısımlarına sahip olabilir
  • Aşamalı Migrasyon: Monolith’lerden kademeli migrasyon mümkündür

Micro Frontend Mimarisi Türleri

İşte her biri farklı özelliklere ve kullanım durumlarına sahip dört ana mimari kalıp:

1. Server-Side Template Composition

Farklı servislerin HTML fragment’larını render ettiği ve sunucuda compose edildiği en basit yaklaşım.

// Birden fazla micro frontend'i compose eden gateway servisi
import express from 'express';
import fetch from 'node-fetch';

const app = express();

app.get('/', async (req, res) => {
  try {
    // Farklı servislerden fragment'ları fetch et
    const [header, navigation, content, footer] = await Promise.all([
      fetch('http://header-service/fragment').then(r => r.text()),
      fetch('http://nav-service/fragment').then(r => r.text()),
      fetch('http://content-service/fragment').then(r => r.text()),
      fetch('http://footer-service/fragment').then(r => r.text())
    ]);

    const html = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>Compose Edilmiş Uygulama</title>
        </head>
        <body>
          ${header}
          ${navigation}
          <main>${content}</main>
          ${footer}
        </body>
      </html>
    `;

    res.send(html);
  } catch (error) {
    res.status(500).send('Error composing page');
  }
});

Artıları: Anlaması basit, iyi SEO, JavaScript olmadan çalışır Eksileri: Sınırlı etkileşim, navigasyon için sayfa yenileme, paylaşılan state zorlukları

Ne zaman kullanılır: İçerik ağırlıklı siteler, SEO kritik olduğunda, server-side geliştirmede rahat takımlar

2. Build-Time Integration

Micro frontend’ler npm paketleri olarak yayınlanır ve build zamanında compose edilir.

// Shell uygulamasının Package.json'ı
{
  "dependencies": {
    "@company/header-mf": "^1.2.0",
    "@company/product-catalog-mf": "^2.1.5",
    "@company/checkout-mf": "^1.8.2"
  }
}

// Shell uygulaması
import React from 'react';
import { Header } from '@company/header-mf';
import { ProductCatalog } from '@company/product-catalog-mf';
import { Checkout } from '@company/checkout-mf';

const App: React.FC = () => {
  return (
    <div>
      <Header />
      <main>
        <ProductCatalog />
        <Checkout />
      </main>
    </div>
  );
};

export default App;
// Micro frontend paketi (header-mf)
import React from 'react';

export interface HeaderProps {
  user?: {
    name: string;
    avatar: string;
  };
  onLogout?: () => void;
}

export const Header: React.FC<HeaderProps> = ({ user, onLogout }) => {
  return (
    <header className="bg-blue-600 text-white p-4">
      <div className="flex justify-between items-center">
        <h1>Uygulamam</h1>
        {user && (
          <div className="flex items-center gap-2">
            <img src={user.avatar} alt={user.name} className="w-8 h-8 rounded-full" />
            <span>{user.name}</span>
            <button onClick={onLogout}>Çıkış</button>
          </div>
        )}
      </div>
    </header>
  );
};

Artıları: Type safety, paylaşılan bağımlılık optimizasyonu, tanıdık geliştirme deneyimi Eksileri: Koordineli deployment’lar, versiyon yönetimi karmaşıklığı, gerçekten bağımsız değil

Ne zaman kullanılır: Micro frontend faydaları istiyorsanız ama koordineli deployment’ları tolere edebiliyorsanız

3. JavaScript ile Runtime Integration

Micro frontend’lerin runtime’da yüklendiği ve entegre edildiği en esnek yaklaşım.

// Micro frontend registry
interface MicroFrontendConfig {
  name: string;
  url: string;
  scope: string;
  module: string;
}

class MicroFrontendRegistry {
  private configs: Map<string, MicroFrontendConfig> = new Map();
  private loadedModules: Map<string, any> = new Map();

  register(config: MicroFrontendConfig) {
    this.configs.set(config.name, config);
  }

  async load(name: string): Promise<any> {
    if (this.loadedModules.has(name)) {
      return this.loadedModules.get(name);
    }

    const config = this.configs.get(name);
    if (!config) {
      throw new Error(`Micro frontend ${name} kayıtlı değil`);
    }

    // Hata yönetimi ile dinamik import
    try {
      await this.loadScript(config.url);
      const container = (window as any)[config.scope];

      if (!container) {
        throw new Error(`Container ${config.scope} bulunamadı`);
      }

      await container.init({
        react: () => Promise.resolve(React),
        'react-dom': () => Promise.resolve(ReactDOM),
      });

      const factory = await container.get(config.module);
      const Module = factory();

      this.loadedModules.set(name, Module);
      return Module;
    } catch (error) {
      console.error(`Micro frontend ${name} yüklenemedi:`, error);
      throw error;
    }
  }

  private loadScript(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = url;
      script.onload = () => resolve();
      script.onerror = () => reject(new Error(`Script yüklenemedi: ${url}`));
      document.head.appendChild(script);
    });
  }
}

// Shell uygulamasında kullanım
const registry = new MicroFrontendRegistry();

registry.register({
  name: 'product-catalog',
  url: 'http://localhost:3001/remoteEntry.js',
  scope: 'productCatalog',
  module: './ProductCatalog'
});

const DynamicMicroFrontend: React.FC<{ name: string }> = ({ name }) => {
  const [Component, setComponent] = useState<React.ComponentType | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    registry.load(name)
      .then(Module => {
        setComponent(() => Module.default || Module);
        setError(null);
      })
      .catch(err => {
        setError(err.message);
        setComponent(null);
      })
      .finally(() => setLoading(false));
  }, [name]);

  if (loading) return <div>{name} yükleniyor...</div>;
  if (error) return <div>{name} yükleme hatası: {error}</div>;
  if (!Component) return <div>{name} bileşeni bulunamadı</div>;

  return <Component />;
};

Artıları: Gerçek bağımsızlık, farklı teknoloji stack’leri mümkün, runtime esnekliği Eksileri: Karmaşıklık, runtime hataları, performans overhead’i, hata ayıklama zorlukları

Ne zaman kullanılır: Birden fazla takıma sahip büyük organizasyonlar, teknoloji çeşitliliği ihtiyacı

4. Iframe Tabanlı Integration

Tam izolasyon için iframe’leri kullanan en izole yaklaşım.

// postMessage iletişimi ile Iframe micro frontend wrapper
interface IframeMicroFrontendProps {
  src: string;
  name: string;
  onMessage?: (data: any) => void;
}

const IframeMicroFrontend: React.FC<IframeMicroFrontendProps> = ({
  src,
  name,
  onMessage
}) => {
  const iframeRef = useRef<HTMLIFrameElement>(null);
  const [isLoaded, setIsLoaded] = useState(false);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      // Güvenlik için origin doğrula
      if (event.origin !== new URL(src).origin) {
        return;
      }

      if (event.data.source === name) {
        onMessage?.(event.data.payload);
      }
    };

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [src, name, onMessage]);

  const sendMessage = (data: any) => {
    if (iframeRef.current?.contentWindow) {
      iframeRef.current.contentWindow.postMessage({
        source: 'shell',
        target: name,
        payload: data
      }, new URL(src).origin);
    }
  };

  return (
    <div className="micro-frontend-container">
      {!isLoaded && <div>{name} yükleniyor...</div>}
      {error && <div>Hata: {error}</div>}
      <iframe
        ref={iframeRef}
        src={src}
        onLoad={() => setIsLoaded(true)}
        onError={() => setError(`${name} yüklenemedi`)}
        style={{
          width: '100%',
          border: 'none',
          minHeight: '400px'
        }}
        title={name}
        sandbox="allow-scripts allow-same-origin allow-forms"
      />
    </div>
  );
};

// Micro frontend içinde (iframe içeriği)
const MicroFrontendApp: React.FC = () => {
  const [data, setData] = useState<any>(null);

  useEffect(() => {
    const handleMessage = (event: MessageEvent) => {
      if (event.data.target === 'product-catalog') {
        setData(event.data.payload);
      }
    };

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, []);

  const sendDataToShell = (payload: any) => {
    window.parent.postMessage({
      source: 'product-catalog',
      payload
    }, '*');
  };

  return (
    <div>
      <h2>Ürün Katalogu Micro Frontend</h2>
      {/* Micro frontend içeriğiniz */}
    </div>
  );
};

Artıları: Tam izolasyon, güvenlik, farklı domain’ler mümkün, CSS izolasyonu Eksileri: Sınırlı iletişim, SEO zorlukları, performans overhead’i, UX düşünceleri

Ne zaman kullanılır: Güvenlik öncelik, legacy entegrasyon, üçüncü taraf içerik

Gerçek Bir Hata Ayıklama Hikayesi: Kaybolan Stiller Vakası

Yaygın micro frontend tuzaklarını gösteren bir hata ayıklama hikayesi paylaşayım. Yukarıdakine benzer bir runtime entegrasyon sistemi implement etmiştik ve geliştirme ortamında her şey mükemmel çalışıyordu. Ancak production’da, ürün katalogu micro frontend’imizde eksik stiller hakkında raporlar almaya başladık.

Belirtiler şaşırtıcıydı:

  • Stiller micro frontend tek başına çalıştığında gayet iyi çalışıyordu
  • Sorun sadece production’da oluyordu, geliştirme ortamında değil
  • Aralıklıydı - bazen stiller yükleniyordu, bazen yüklenmiyordu

Saatlerce araştırmadan sonra, temel nedeni keşfettik: CSS yükleme race condition’ları.

// Problemli kod
const ProductCatalogMF: React.FC = () => {
  useEffect(() => {
    // Bu, component mount'tan sonra CSS yüklüyordu
    import('./styles.css');
  }, []);

  return <div className="product-grid">...</div>;
};

Sorun şuydu: production’da, daha agresif minification ve CDN cache’leme ile CSS import’u component zaten render olduktan sonra tamamlanıyordu. Çözüm daha sağlam bir yükleme stratejisi gerektiriyordu:

// Uygun CSS yükleme ile düzeltilmiş versiyon
const MicroFrontendLoader = {
  async loadWithStyles(name: string, cssUrls: string[] = []) {
    // Önce CSS'i yükle
    await Promise.all(
      cssUrls.map(url => this.loadStylesheet(url))
    );

    // Sonra component'i yükle
    return await registry.load(name);
  },

  loadStylesheet(url: string): Promise<void> {
    return new Promise((resolve, reject) => {
      // Zaten yüklenmiş mi kontrol et
      if (document.querySelector(`link[href="${url}"]`)) {
        resolve();
        return;
      }

      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = url;
      link.onload = () => resolve();
      link.onerror = () => reject(new Error(`CSS yüklenemedi: ${url}`));
      document.head.appendChild(link);
    });
  }
};

Bu deneyim bize şunların önemini öğretti:

  1. Micro frontend mimarilerinde doğru yükleme sıralaması
  2. Environment parity - production sorunları genellikle geliştirme ortamında göstermez
  3. Monitoring ve gözlemlenebilirlik - bu sorunları erken yakalamak için CSS yükleme takibi ekledik

Performans Düşünceleri

Micro frontend’ler benzersiz performans zorlukları sunar:

Bundle Boyutu ve Duplikasyon

Birden fazla micro frontend genellikle aynı bağımlılıkları ship eder, bu da şişkin bundle’lara yol açar.

// Paylaşılan bağımlılıklar için Webpack konfigürasyonu
// Not: Module Federation 2.0 gelişmiş performans ve daha iyi paket optimizasyonu sunar
const ModuleFederationPlugin = require('@module-federation/webpack');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        productCatalog: 'productCatalog@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: {
          singleton: true,
          requiredVersion: '^18.0.0',
        },
        'react-dom': {
          singleton: true,
          requiredVersion: '^18.0.0',
        },
        // Ortak yardımcıları paylaş
        lodash: {
          singleton: false, // Gerekirse birden fazla versiyona izin ver
        }
      },
    }),
  ],
};

Yükleme Performansı

Progressive yükleme stratejileri uygulayın:

// Progressive micro frontend yükleme
const ProgressiveMicroFrontend: React.FC<{
  name: string;
  priority: 'high' | 'medium' | 'low';
}> = ({ name, priority }) => {
  const [shouldLoad, setShouldLoad] = useState(priority === 'high');
  const isVisible = useIntersectionObserver();

  useEffect(() => {
    if (priority === 'medium' && isVisible) {
      setShouldLoad(true);
    } else if (priority === 'low') {
      // Ana içerik hazır olduktan sonra yükle
      const timer = setTimeout(() => setShouldLoad(true), 2000);
      return () => clearTimeout(timer);
    }
  }, [isVisible, priority]);

  if (!shouldLoad) {
    return <div>{name} yükleniyor...</div>;
  }

  return <DynamicMicroFrontend name={name} />;
};

Doğru Mimariyi Seçmek

Seçim, özel kısıtlarınıza bağlıdır:

FaktörServer-SideBuild-TimeRuntimeIframe
Takım BağımsızlığıDüşükOrtaYüksekYüksek
Teknoloji ÇeşitliliğiOrtaDüşükYüksekYüksek
PerformansYüksekYüksekOrtaDüşük
KarmaşıklıkDüşükOrtaYüksekOrta
SEOMükemmelİyiZayıfZayıf
Geliştirme DeneyimiİyiMükemmelOrtaZayıf

Sırada Ne Var?

Artık temel micro frontend kalıplarını anladığınıza göre, pratik implementasyona daha derin dalmaya hazırsınız.

Kısım 2: Module Federation ve Uygulama Kalıpları’na devam edin burada şunları kapsayacağız:

  • Production’a hazır Module Federation konfigürasyonları
  • Sağlam hata yönetimi ve fallback stratejileri
  • Cross-micro frontend iletişim kalıpları
  • Uygulamalar arası routing koordinasyonu
  • Geliştirme iş akışları ve tooling
  • Production sistemlerinden gerçek hata ayıklama hikayeleri

Anahtar Öğreti: Micro frontend’ler sadece teknik bir kalıp değil - takım yapınız, iş gereksinimleri ve teknik kısıtlarınızın dikkatli değerlendirmesini gerektiren organizasyonel bir kalıptır.

Burada kapsanan temel kalıplar mimari kararlarınızı yönlendirecek, ancak gerçek karmaşıklık entegrasyon katmanında ortaya çıkar - bunu bir sonraki yazıda ele alacağız.


Seri Navigasyonu

  • Kısım 1 (Mevcut): Mimari temelleri
  • Kısım 2: Uygulama kalıpları
  • Kısım 3: İleri düzey kalıplar ve hata ayıklama

Micro Frontend Mimari Rehberi

Micro frontend mimarisine dair 3 bölümlük kapsamlı rehber. Temel kavramlardan ileri düzey pattern'lere ve production debugging stratejilerine kadar.

İlerleme 1 / 3 yazı

Serideki tüm yazılar

İlgili yazılar

Micro Frontend Uygulama Pattern'leri: Pratik Rehber

Micro frontend'leri production'da uygulamak için pratik pattern'ler. Routing, state yönetimi, communication ve deployment stratejileri.

module-federationreacttutorial+2
TypeScript AI SDK Karşılaştırması: Agent Geliştirme için Vercel AI SDK vs OpenAI Agents SDK

AI agent geliştirmek için TypeScript SDK'larının pratik karşılaştırması - Vercel AI SDK, OpenAI Agents SDK ve AWS Bedrock entegrasyonu. Kod örnekleri, karar frameworkleri ve production patternleri içeriyor.

typescriptai-toolsserverless+4
TypeScript Formatlama ve Linting Araçları Karşılaştırması: Biome, Oxlint, ESLint ve Prettier

Modern TypeScript linting ve formatlama araçlarının kapsamlı karşılaştırması - ESLint, Prettier, Biome ve Oxlint - performans ölçümleri, konfigürasyon örnekleri ve migration stratejileriyle.

typescripteslintprettier+7
JavaScript'te SOLID Prensipleri: TypeScript ve React ile Pratik Rehber

SOLID prensiplerininin modern JavaScript geliştirmede nasıl uygulanacağını öğrenin. TypeScript, React hooks ve fonksiyonel pattern'ler ile pratik örnekler - ayrıca ne zaman kullanmalı, ne zaman gereksiz.

typescriptjavascriptreact+5
Effect Öğrenmek: TypeScript Geliştiricileri için Pratik Bir Rehber

Effect'i anlamak, adım adım öğrenmek ve AWS Lambda ile entegre etmek için kapsamlı bir rehber. Gerçek kod örnekleri, yaygın hatalar ve üretim kullanımından pratik desenler içerir.

typescripteffectaws-lambda+5