Skip to content
~/sph.sh

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.

Micro Frontend Serisi Navigasyonu

  • Bölüm 1: Mimari temelleri ve uygulama türleri
  • Bölüm 2 (Şu anda buradasınız): Module Federation, iletişim pattern'leri ve entegrasyon stratejileri
  • Bölüm 3: İleri düzey pattern'ler, performans optimizasyonu ve production hata ayıklama

Ön Koşullar: Bu yazı Bölüm 1'deki kavramlar üzerine kuruludur. Micro frontend'lere yeniyseniz, önce oradan başlayın.


Bölüm 1'de micro frontend'ler için temel mimari pattern'leri keşfettik. Şimdi pratik uygulamaya derinlemesine dalacağız, runtime entegrasyonunda baskın yaklaşım olan Module Federation'a odaklanacağız, ayrıca production sistemlerinde karşılaştığım gerçek dünya iletişim pattern'leri ve hata ayıklama stratejilerini paylaşacağım.

Module Federation Derinlemesine İnceleme

Webpack 5'te tanıtılan Module Federation, runtime micro frontend entegrasyonu için altın standart haline geldi. Basit dinamik import'lardan farklı olarak, sofistike bağımlılık paylaşımı, versiyon yönetimi ve runtime kompozisyon yetenekleri sağlar.

Production'a Hazır Module Federation Sistemi Kurma

Farklı takımların farklı domain'lere sahip olduğu gerçekçi bir e-ticaret uygulaması oluşturalım:

typescript
// apps/shell/webpack.config.js// Not: Yeni özellikler yerine production güvenilirliği için stabil versiyonlar kullanıyoruz.// @module-federation/enhanced yeni paket adı (önceden @module-federation/webpack)const ModuleFederationPlugin = require('@module-federation/enhanced');const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {  mode: 'development',  entry: './src/index.ts',
  module: {    rules: [      {        test: /\.tsx?$/,        use: 'ts-loader',        exclude: /node_modules/,      },      {        test: /\.css$/,        use: ['style-loader', 'css-loader', 'postcss-loader'],      },    ],  },
  plugins: [    new ModuleFederationPlugin({      name: 'shell',      filename: 'remoteEntry.js',
      remotes: {        // Ürün takımının micro frontend'i        products: 'products@http://localhost:3001/remoteEntry.js',        // Sepet takımının micro frontend'i        cart: 'cart@http://localhost:3002/remoteEntry.js',        // Kullanıcı takımının micro frontend'i        user: 'user@http://localhost:3003/remoteEntry.js',      },
      shared: {        // Production'da kanıtlanmış stabil versiyonlar kullanıyoruz - her zaman en yenisi değil        // Bu tüm micro frontend'lerde uyumluluğu garanti ediyor        react: {          singleton: true,          strictVersion: true,          requiredVersion: '^18.2.0',        },        'react-dom': {          singleton: true,          strictVersion: true,          requiredVersion: '^18.2.0',        },        'react-router-dom': {          singleton: true,          requiredVersion: '^6.8.0',        },        // Özel paylaşılan yardımcılar        '@company/design-system': {          singleton: true,          requiredVersion: '^2.1.0',        },        '@company/event-bus': {          singleton: true,          requiredVersion: '^1.0.0',        }      },    }),
    new HtmlWebpackPlugin({      template: './public/index.html',    }),  ],
  resolve: {    extensions: ['.tsx', '.ts', '.js'],  },
  devServer: {    port: 3000,    historyApiFallback: true,    headers: {      'Access-Control-Allow-Origin': '*',    },  },};
typescript
// apps/products/webpack.config.jsconst ModuleFederationPlugin = require('@module-federation/enhanced');
module.exports = {  mode: 'development',  entry: './src/index.ts',
  plugins: [    new ModuleFederationPlugin({      name: 'products',      filename: 'remoteEntry.js',
      exposes: {        './ProductList': './src/components/ProductList',        './ProductDetail': './src/components/ProductDetail',        './ProductSearch': './src/components/ProductSearch',      },
      shared: {        react: {          singleton: true,          requiredVersion: '^18.2.0',        },        'react-dom': {          singleton: true,          requiredVersion: '^18.2.0',        },        'react-router-dom': {          singleton: true,          requiredVersion: '^6.8.0',        },        '@company/design-system': {          singleton: true,          requiredVersion: '^2.1.0',        },        '@company/event-bus': {          singleton: true,          requiredVersion: '^1.0.0',        }      },    }),  ],
  module: {    rules: [      {        test: /\.tsx?$/,        use: 'ts-loader',        exclude: /node_modules/,      },    ],  },
  resolve: {    extensions: ['.tsx', '.ts', '.js'],  },
  devServer: {    port: 3001,    headers: {      'Access-Control-Allow-Origin': '*',    },  },};

Error Boundary'ler ile Güçlü Modül Yükleme

Module Federation'da en büyük zorluklardan biri yükleme hatalarını zarif bir şekilde ele almaktır. İşte production'da test edilmiş bir yaklaşım:

typescript
// src/components/MicroFrontendLoader.tsximport React, { Suspense, lazy, useState, useEffect } from 'react';
interface MicroFrontendConfig {  scope: string;  module: string;  url: string;  fallback?: React.ComponentType;}
interface LoadingState {  isLoading: boolean;  error: Error | null;  retryCount: number;}
const useDynamicScript = (url: string) => {  const [ready, setReady] = useState(false);  const [failed, setFailed] = useState(false);
  useEffect(() => {    if (!url) return;
    const element = document.createElement('script');    element.src = url;    element.type = 'text/javascript';    element.async = true;
    setReady(false);    setFailed(false);
    element.onload = () => {      console.log(`Dinamik Script Yüklendi: ${url}`);      setReady(true);    };
    element.onerror = () => {      console.error(`Dinamik Script Hatası: ${url}`);      setReady(false);      setFailed(true);    };
    document.head.appendChild(element);
    return () => {      console.log(`Dinamik Script Kaldırıldı: ${url}`);      document.head.removeChild(element);    };  }, [url]);
  return { ready, failed };};
const loadComponent = (scope: string, module: string) => {  return async () => {    // Paylaşım kapsamını başlatır. Bu, bu build'den ve tüm remote'lardan bilinen sağlanan modüllerle doldurur    await __webpack_init_sharing__('default');
    const container = (window as any)[scope]; // veya container'ı başka bir yerden alın    if (!container) {      throw new Error(`Container '${scope}' bulunamadı`);    }
    // Container'ı başlat, paylaşılan modüller sağlayabilir    await container.init(__webpack_share_scopes__.default);
    const factory = await (window as any)[scope].get(module);    const Module = factory();    return Module;  };};
class MicroFrontendErrorBoundary extends React.Component<  { children: React.ReactNode; fallback: React.ComponentType },  { hasError: boolean; error: Error | null }> {  constructor(props: any) {    super(props);    this.state = { hasError: false, error: null };  }
  static getDerivedStateFromError(error: Error) {    return { hasError: true, error };  }
  componentDidCatch(error: Error, errorInfo: any) {    console.error('Micro Frontend Hatası:', error, errorInfo);
    // İzleme servisine gönder    if (typeof window !== 'undefined' && (window as any).analytics) {      (window as any).analytics.track('Micro Frontend Hatası', {        error: error.message,        stack: error.stack,        componentStack: errorInfo.componentStack,      });    }  }
  render() {    if (this.state.hasError) {      const Fallback = this.props.fallback;      return <Fallback />;    }
    return this.props.children;  }}
export const MicroFrontendLoader: React.FC<{  config: MicroFrontendConfig;  props?: Record<string, any>;}> = ({ config, props = {} }) => {  const { ready, failed } = useDynamicScript(config.url);  const [loadingState, setLoadingState] = useState<LoadingState>({    isLoading: false,    error: null,    retryCount: 0,  });
  useEffect(() => {    if (ready && !loadingState.isLoading) {      setLoadingState(prev => ({ ...prev, isLoading: true, error: null }));    }  }, [ready]);
  const handleRetry = () => {    if (loadingState.retryCount < 3) {      setLoadingState(prev => ({        ...prev,        retryCount: prev.retryCount + 1,        error: null,        isLoading: true,      }));
      // Script'i yeniden yüklemeye zorla      window.location.reload();    }  };
  if (failed || (loadingState.error && loadingState.retryCount >= 3)) {    const Fallback = config.fallback || DefaultErrorFallback;    return <Fallback onRetry={handleRetry} />;  }
  if (!ready) {    return <LoadingFallback />;  }
  const Component = lazy(loadComponent(config.scope, config.module));
  return (    <MicroFrontendErrorBoundary fallback={config.fallback || DefaultErrorFallback}>      <Suspense fallback={<LoadingFallback />}>        <Component {...props} />      </Suspense>    </MicroFrontendErrorBoundary>  );};
const LoadingFallback: React.FC = () => (  <div className="animate-pulse">    <div className="h-4 bg-gray-300 rounded w-3/4 mb-2"></div>    <div className="h-4 bg-gray-300 rounded w-1/2"></div>  </div>);
const DefaultErrorFallback: React.FC<{ onRetry?: () => void }> = ({ onRetry }) => (  <div className="p-4 border border-red-300 rounded bg-red-50">    <h3 className="text-red-800 font-semibold">Bir şeyler yanlış gitti</h3>    <p className="text-red-600 text-sm mt-1">      Bu bölüm yüklenemedi. Lütfen sayfayı yenilemeyi deneyin.    </p>    {onRetry && (      <button        onClick={onRetry}        className="mt-2 px-3 py-1 bg-red-600 text-white rounded text-sm"      >        Tekrar Dene      </button>    )}  </div>);

Cross-Micro Frontend İletişimi

Micro frontend mimarisinin en zorlu yönlerinden biri, bağımsız olarak geliştirilmiş ve dağıtılmış uygulamalar arasında iletişimi sağlamaktır. İşte en etkili bulduğum pattern'ler:

1. Event-Driven İletişim

typescript
// @company/event-bus - Paylaşılan event bus paketiinterface EventBusEvent {  type: string;  payload: any;  source: string;  timestamp: number;}
class EventBus {  private listeners: Map<string, Array<(event: EventBusEvent) => void>> = new Map();  private eventHistory: EventBusEvent[] = [];  private maxHistorySize = 50;
  subscribe(eventType: string, callback: (event: EventBusEvent) => void): () => void {    if (!this.listeners.has(eventType)) {      this.listeners.set(eventType, []);    }
    this.listeners.get(eventType)!.push(callback);
    // Unsubscribe fonksiyonunu döndür    return () => {      const callbacks = this.listeners.get(eventType);      if (callbacks) {        const index = callbacks.indexOf(callback);        if (index > -1) {          callbacks.splice(index, 1);        }      }    };  }
  publish(type: string, payload: any, source: string = 'unknown') {    const event: EventBusEvent = {      type,      payload,      source,      timestamp: Date.now(),    };
    // Geçmişe ekle    this.eventHistory.push(event);    if (this.eventHistory.length > this.maxHistorySize) {      this.eventHistory.shift();    }
    // Dinleyicileri bilgilendir    const callbacks = this.listeners.get(type) || [];    callbacks.forEach(callback => {      try {        callback(event);      } catch (error) {        console.error(`${type} için event listener'da hata:`, error);      }    });
    // Development'ta debug loglama    if (process.env.NODE_ENV === 'development') {      console.log(`[EventBus] ${type}:`, payload);    }  }
  getHistory(eventType?: string): EventBusEvent[] {    if (eventType) {      return this.eventHistory.filter(event => event.type === eventType);    }    return [...this.eventHistory];  }
  // Geç yüklenen micro frontend'ler için event'leri tekrar oynat  replayEvents(eventType: string, callback: (event: EventBusEvent) => void) {    const pastEvents = this.eventHistory.filter(event => event.type === eventType);    pastEvents.forEach(event => callback(event));  }}
export const eventBus = new EventBus();
// Daha kolay kullanım için React hookexport const useEventBus = (  eventType: string,  callback: (event: EventBusEvent) => void,  deps: React.DependencyList = []) => {  useEffect(() => {    const unsubscribe = eventBus.subscribe(eventType, callback);    return unsubscribe;  }, deps);
  const publish = useCallback((payload: any, source?: string) => {    eventBus.publish(eventType, payload, source);  }, [eventType]);
  return { publish };};

2. Micro Frontend'lerde Pratik Kullanım

typescript
// products/src/components/ProductList.tsximport React, { useState, useEffect } from 'react';import { useEventBus } from '@company/event-bus';
interface Product {  id: string;  name: string;  price: number;  image: string;}
export const ProductList: React.FC = () => {  const [products, setProducts] = useState<Product[]>([]);  const [filters, setFilters] = useState<any>(null);
  // Arama micro frontend'inden filtre değişikliklerini dinle  useEventBus('search:filters-changed', (event) => {    setFilters(event.payload);  });
  // Geri bildirim göstermek için sepet güncellemelerini dinle  useEventBus('cart:item-added', (event) => {    // Başarı bildirimi göster    showNotification(`${event.payload.productName} sepete eklendi!`);  });
  const { publish } = useEventBus('products:product-selected', () => {});
  const handleProductClick = (product: Product) => {    publish({      productId: product.id,      productName: product.name,      source: 'product-list',    }, 'products');  };
  useEffect(() => {    // Değiştiğinde filtreleri uygula    if (filters) {      // Ürünleri filtreleme mantığı      const filtered = applyFilters(products, filters);      setProducts(filtered);    }  }, [filters]);
  return (    <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">      {products.map(product => (        <div          key={product.id}          className="border rounded-lg p-4 cursor-pointer hover:shadow-lg"          onClick={() => handleProductClick(product)}        >          <img src={product.image} alt={product.name} className="w-full h-48 object-cover" />          <h3 className="font-semibold mt-2">{product.name}</h3>          <p className="text-gray-600">${product.price}</p>        </div>      ))}    </div>  );};
typescript
// cart/src/components/CartButton.tsximport React, { useState, useEffect } from 'react';import { useEventBus } from '@company/event-bus';
export const CartButton: React.FC = () => {  const [itemCount, setItemCount] = useState(0);  const [isAnimating, setIsAnimating] = useState(false);
  // Ürün eklemelerini dinle  useEventBus('products:product-selected', (event) => {    addToCart(event.payload.productId);  });
  const { publish } = useEventBus('cart:item-added', () => {});
  const addToCart = async (productId: string) => {    try {      // Sepete ekleme mantığı      const response = await fetch('/api/cart/add', {        method: 'POST',        headers: { 'Content-Type': 'application/json' },        body: JSON.stringify({ productId }),      });
      if (response.ok) {        const result = await response.json();        setItemCount(prev => prev + 1);
        // Animasyonu tetikle        setIsAnimating(true);        setTimeout(() => setIsAnimating(false), 500);
        // Diğer micro frontend'leri bilgilendir        publish({          productId,          productName: result.productName,          newTotal: result.cartTotal,        }, 'cart');      }    } catch (error) {      console.error('Sepete ekleme başarısız:', error);    }  };
  return (    <button      className={`relative p-2 bg-blue-600 text-white rounded-full ${        isAnimating ? 'animate-pulse' : ''      }`}    >      <ShoppingCartIcon className="w-6 h-6" />      {itemCount > 0 && (        <span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">          {itemCount}        </span>      )}    </button>  );};

Routing Stratejileri

Micro frontend mimarilerinde routing, çakışmaları önlemek ve sorunsuz bir kullanıcı deneyimi sağlamak için dikkatli koordinasyon gerektirir:

1. Shell Kontrollü Routing

typescript
// shell/src/App.tsximport React, { Suspense } from 'react';import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';import { MicroFrontendLoader } from './components/MicroFrontendLoader';
const App: React.FC = () => {  return (    <BrowserRouter>      <div className="min-h-screen bg-gray-50">        {/* Global navigasyon */}        <nav className="bg-white shadow-sm border-b">          <div className="max-w-7xl mx-auto px-4">            <div className="flex justify-between h-16">              <div className="flex">                <Link to="/" className="flex items-center px-4 text-lg font-semibold">                  E-Ticaret                </Link>                <div className="flex space-x-8 ml-8">                  <Link to="/products" className="flex items-center px-3 py-2 hover:text-blue-600">                    Ürünler                  </Link>                  <Link to="/categories" className="flex items-center px-3 py-2 hover:text-blue-600">                    Kategoriler                  </Link>                </div>              </div>
              {/* Kullanıcı hesabı micro frontend */}              <div className="flex items-center">                <MicroFrontendLoader                  config={{                    scope: 'user',                    module: './UserMenu',                    url: 'http://localhost:3003/remoteEntry.js',                  }}                />              </div>            </div>          </nav>
        {/* Ana içerik alanı */}        <main className="max-w-7xl mx-auto px-4 py-8">          <Routes>            <Route path="/" element={<Navigate to="/products" replace />} />
            {/* Products micro frontend tüm /products/* route'larını yönetir */}            <Route              path="/products/*"              element={                <MicroFrontendLoader                  config={{                    scope: 'products',                    module: './ProductsApp',                    url: 'http://localhost:3001/remoteEntry.js',                  }}                />              }            />
            {/* Cart micro frontend */}            <Route              path="/cart/*"              element={                <MicroFrontendLoader                  config={{                    scope: 'cart',                    module: './CartApp',                    url: 'http://localhost:3002/remoteEntry.js',                  }}                />              }            />
            {/* Bilinmeyen route'lar için fallback */}            <Route path="*" element={<NotFoundPage />} />          </Routes>        </main>      </div>    </BrowserRouter>  );};

2. Micro Frontend İç Routing

typescript
// products/src/ProductsApp.tsximport React from 'react';import { Routes, Route, useLocation } from 'react-router-dom';import { ProductList } from './components/ProductList';import { ProductDetail } from './components/ProductDetail';import { ProductSearch } from './components/ProductSearch';
export const ProductsApp: React.FC = () => {  const location = useLocation();
  // Micro frontend için analytics takibi  useEffect(() => {    if (typeof window !== 'undefined' && (window as any).analytics) {      (window as any).analytics.page('Products', {        path: location.pathname,        microfrontend: 'products',      });    }  }, [location.pathname]);
  return (    <div className="products-app">      <Routes>        {/* Not: path'ler /products'a göreceli */}        <Route index element={<ProductList />} />        <Route path="search" element={<ProductSearch />} />        <Route path="category/:categoryId" element={<ProductList />} />        <Route path=":productId" element={<ProductDetail />} />      </Routes>    </div>  );};

Hata Ayıklama Hikayesi: Kaybolan Route'ların Gizemi

İşte micro frontend routing'in karmaşıklığını gösteren gerçek bir hata ayıklama zorluğu. Belirli ürün detay sayfalarının rastgele 404 hatası gösterdiği bir production sorunumuz vardı, ancak sadece bazı kullanıcılar için ve sadece bazen.

Araştırma, routing kurulumumuzda bir yarış durumu olduğunu ortaya çıkardı:

typescript
// Sorunlu kodconst ProductsApp: React.FC = () => {  const [isLoaded, setIsLoaded] = useState(false);
  useEffect(() => {    // Bu yarış durumuna neden oluyordu    setTimeout(() => setIsLoaded(true), 100);  }, []);
  if (!isLoaded) {    return <div>Yükleniyor...</div>;  }
  return (    <Routes>      <Route path=":productId" element={<ProductDetail />} />    </Routes>  );};

Sorun, shell'deki React Router'ın micro frontend kendi route'larını başlatmayı bitirmeden önce route'ları eşleştirmeye çalışmasıydı. Daha hızlı bağlantıya sahip kullanıcılar yarış durumuna daha sık çarpıyordu.

Çözüm, shell ve micro frontend routing arasında koordinasyon gerektirdi:

typescript
// Uygun route kaydı ile düzeltilmiş versiyonimport { useEventBus } from '@company/event-bus';
const ProductsApp: React.FC = () => {  const [routesReady, setRoutesReady] = useState(false);  const { publish } = useEventBus('routing:micro-frontend-ready', () => {});
  useEffect(() => {    // Mevcut route'ları shell'e kaydet    publish({      microfrontend: 'products',      routes: [        '/products',        '/products/search',        '/products/category/:categoryId',        '/products/:productId'      ]    }, 'products');
    setRoutesReady(true);  }, [publish]);
  if (!routesReady) {    return <LoadingSpinner />;  }
  return (    <Routes>      <Route index element={<ProductList />} />      <Route path="search" element={<ProductSearch />} />      <Route path="category/:categoryId" element={<ProductList />} />      <Route path=":productId" element={<ProductDetail />} />    </Routes>  );};

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

  1. Shell ve micro frontend'ler arasında açık route kaydı
  2. Routing'e müdahale etmeyen uygun yükleme durumları
  3. Production'da route çözümlemenin kapsamlı izlenmesi

Geliştirme ve Test Stratejileri

Yerel Geliştirme Kurulumu

typescript
// scripts/dev-all.js - Tüm micro frontend'leri yerel olarak çalıştırmak için scriptconst { spawn } = require('child_process');const path = require('path');
const services = [  { name: 'shell', port: 3000, path: './apps/shell' },  { name: 'products', port: 3001, path: './apps/products' },  { name: 'cart', port: 3002, path: './apps/cart' },  { name: 'user', port: 3003, path: './apps/user' },];
const processes = [];
services.forEach(service => {  console.log(`${service.name} ${service.port} portunda başlatılıyor...`);
  const process = spawn('npm', ['run', 'dev'], {    cwd: path.resolve(service.path),    stdio: 'inherit',    shell: true,    env: { ...process.env, PORT: service.port.toString() }  });
  processes.push(process);});
// Zarif kapanışprocess.on('SIGTERM', () => {  processes.forEach(p => p.kill());});
process.on('SIGINT', () => {  processes.forEach(p => p.kill());  process.exit(0);});

Entegrasyon Testi

typescript
// tests/integration/micro-frontend-integration.test.tsimport { test, expect, Page } from '@playwright/test';
test.describe('Micro Frontend Entegrasyonu', () => {  test('tüm micro frontend\'leri doğru yüklemeli', async ({ page }) => {    await page.goto('http://localhost:3000');
    // Shell'in yüklenmesini bekle    await expect(page.locator('[data-testid="shell-loaded"]')).toBeVisible();
    // Micro frontend'lerin yüklendiğini kontrol et    await expect(page.locator('[data-testid="products-mf"]')).toBeVisible();    await expect(page.locator('[data-testid="user-menu-mf"]')).toBeVisible();  });
  test('micro frontend iletişimini yönetmeli', async ({ page }) => {    await page.goto('http://localhost:3000/products');
    // Bir ürüne tıkla    await page.click('[data-testid="product-card"]:first-child');
    // Sepetin güncellendiğini doğrula    await expect(page.locator('[data-testid="cart-count"]')).toContainText('1');
    // Bildirimin göründüğünü doğrula    await expect(page.locator('[data-testid="notification"]')).toContainText('sepete eklendi');  });
  test('micro frontend hatalarını zarif bir şekilde ele almalı', async ({ page }) => {    // Bir micro frontend için ağ hatasını simüle et    await page.route('**/products/remoteEntry.js', route => {      route.abort();    });
    await page.goto('http://localhost:3000');
    // Fallback UI göstermeli    await expect(page.locator('[data-testid="products-fallback"]')).toBeVisible();
    // Diğer micro frontend'ler hala çalışmalı    await expect(page.locator('[data-testid="user-menu-mf"]')).toBeVisible();  });});

Sıradaki Konular

Artık Module Federation ile micro frontend'leri uygulamak için temel bilgilere sahipsiniz, ayrıca güçlü iletişim ve routing pattern'lerine de sahipsiniz. Ancak production sistemleri daha sofistike yaklaşımlar gerektirir.

Bölüm 3: İleri Düzey Pattern'ler, Performans ve Hata Ayıklama için devam edin:

  • Dağıtık frontend'ler arasında ileri düzey state yönetimi
  • Performans optimizasyonu ve bundle analiz teknikleri
  • Production hata ayıklama hikayeleri ve izleme stratejileri
  • Cross-origin iletişim için güvenlik pattern'leri
  • Bellek sızıntısı tespiti ve çözümü
  • Monolith'lerden migrasyon stratejileri

Önemli İçgörü: Burada ele alınan pattern'ler çoğu kullanım durumu için iyi çalışır, ancak gerçek zorluklar ölçek, karmaşık state etkileşimleri ve production ortamlarındaki kaçınılmaz edge case'lerle uğraşırken ortaya çıkar.

Paylaşılan hata ayıklama hikayeleri karşılaşacağınız yaygın sorunları temsil eder. Her micro frontend sistemi benzersiz karmaşıklıklar getirir, ancak bu temel pattern'leri anlamak onları daha etkili bir şekilde yönetmenize yardımcı olacaktır.


Seri Navigasyonu

  • Bölüm 1: Mimari temelleri
  • Bölüm 2 (Mevcut): Uygulama pattern'leri
  • Bölüm 3: İleri düzey pattern'ler & 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.

İlerleme2/3 yazı tamamlandı

İlgili Yazılar