Micro Frontend Uygulama Pattern'leri: Pratik Rehber
Module Federation kurulumu, micro frontend'ler arası iletişim, routing stratejileri ve bölünmüş routing'i yük altında bozan yarış durumları.
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: Aşağıdaki bölümler Bölüm 1’deki kavramları veri kabul eder. Micro frontend’lere yeniyseniz, önce oradan başlayın.
Bölüm 1 micro frontend’ler için temel mimari pattern’leri ele aldı. Module Federation, runtime entegrasyonunda baskın yaklaşımdır ve asıl pratik iş onun kurulumunda, etrafındaki iletişim pattern’lerinde ve production sistemlerinin gerektirdiği hata ayıklama stratejilerinde yatar.
Module Federation’a Yakından Bakış#
Webpack 5 ile gelen Module Federation, runtime entegrasyon işinin büyük bölümünü üstleniyor. Dinamik import’ların verdiğinin ötesinde bağımlılık paylaşımı, versiyon uzlaşımı ve runtime kompozisyon sunar.
Production’a Hazır Module Federation Kurulumu#
Farklı takımların farklı domain’lere sahip olduğu gerçekçi bir e-ticaret uygulaması oluşturalım:
// apps/shell/webpack.config.js
// Not: Yeni özellikler yerine production güvenilirliği için stabil versiyonlar kullanıyoruz.
// @module-federation/enhanced, Module Federation 2.0'ı sağlar; webpack 5'in yerleşik
// plugin'i hâlâ webpack.container.ModuleFederationPlugin altında
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
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': '*',
},
},
};
// apps/products/webpack.config.js
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');
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’lerle Dayanıklı Modül Yükleme#
Module Federation’da en büyük zorluklardan biri yükleme hatalarını zarif bir şekilde ele almaktır. Aşağıdaki loader, yüklenemeyen bir remote’un sayfanın geri kalanını da götürmesini engeller:
// src/components/MicroFrontendLoader.tsx
import 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. Küçük bir replay buffer’ı olan paylaşılan event bus çoğu durumu karşılar; geç yüklenen remote’ları senkron tutan da bu replay kısmıdır:
1. Event-Driven İletişim#
// @company/event-bus - Paylaşılan event bus paketi
import { useCallback, useEffect } from 'react';
interface 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 hook
export 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#
// products/src/components/ProductList.tsx
import 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>
);
};
// cart/src/components/CartButton.tsx
import 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#
// shell/src/App.tsx
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate, Link } 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>
</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 İçinde Routing#
// products/src/ProductsApp.tsx
import React, { useEffect } 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>
);
};
Route Kaydında Yarış Durumu#
Bölünmüş routing’in dışarıdan rastgele görünen bir hata biçimi var: bir ürün detay sayfası bazı kullanıcılarda, bazen 404 döner ve sayfayı yenilemek sorunu geçirir. Sebep genellikle shell’in router’ı ile route’larını henüz kaydetmemiş remote arasındaki yarıştır.
Bunu tetikleyen kod şu şekilde:
// Sorunlu kod
const ProductsApp: React.FC = () => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
// Hazır olma sinyali yerine sabit zamanlayıcı kullanmak yarış penceresini açıyor
setTimeout(() => setIsLoaded(true), 100);
}, []);
if (!isLoaded) {
return <div>Yükleniyor...</div>;
}
return (
<Routes>
<Route path=":productId" element={<ProductDetail />} />
</Routes>
);
};
Shell’deki React Router, micro frontend kendi route’larını başlatmayı bitirmeden eşleştirmeye çalışır. Gecikme bir hazır olma sinyali değil sabit bir zamanlayıcı olduğu için pencere yükleme zamanlamasına göre açılıp kapanır; 404’lerin rastgele görünmesinin sebebi budur.
Çözüm, hazır olma durumunu açık hale getirmek: remote önce route’larını duyurur, sonra render eder.
// Uygun route kaydı ile düzeltilmiş versiyon
import { 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>
);
};
Düzeltmeyi kalıcı kılan üç özellik var:
- Shell ve micro frontend’ler arasında açık route kaydı
- Routing’e müdahale etmeyen uygun yükleme durumları
- Production’da route çözümlemenin kapsamlı izlenmesi
Geliştirme ve Test Stratejileri#
Yerel Geliştirme Kurulumu#
// scripts/dev-all.js - Tüm micro frontend'leri yerel olarak çalıştırmak için script
const { 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...`);
// Not: buna `process` adını vermeyin - aşağıda okunan global'i gölgeler
const child = spawn('npm', ['run', 'dev'], {
cwd: path.resolve(service.path),
stdio: 'inherit',
shell: true,
env: { ...process.env, PORT: service.port.toString() }
});
processes.push(child);
});
// Zarif kapanış
process.on('SIGTERM', () => {
processes.forEach(p => p.kill());
});
process.on('SIGINT', () => {
processes.forEach(p => p.kill());
process.exit(0);
});
Entegrasyon Testi#
// tests/integration/micro-frontend-integration.test.ts
import { test, expect } 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#
Paylaşılan event bus ve shell’in sahiplendiği routing ile kurulan Module Federation, varsayılan olarak alınmaya değer kurulumdur: takımlar bağımsız deploy eder ve bunun için ikinci bir runtime işletmek gerekmez. State’in neredeyse tamamı uygulamalar arasında ortaksa, tüm route’lar zaten tek takımın elindeyse veya bundle bütçesi paylaşılan bağımlılık grafiğini kaldırmıyorsa bu varsayılanı bırakın. Üç durumda da modüler monolith daha ucuza gelir.
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
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
Kaynaklar#
- Micro Frontends - Martin Fowler (yeni sekmede açılır) - Micro frontend pattern’leri, routing stratejileri ve uygulamalar arası iletişim üzerine referans makale
- Module Federation Kılavuzu - module-federation.io (yeni sekmede açılır) - Kurulum, dinamik remote’lar ve runtime plugin’lerini kapsayan resmi Module Federation 2.0 dokümantasyonu
- ModuleFederationPlugin - webpack (yeni sekmede açılır) - webpack ModuleFederationPlugin için tam yapılandırma referansı
- single-spa Önerilen Kurulum (yeni sekmede açılır) - single-spa ile micro frontend uygulamalarını yapılandırmak için önerilen pratikler
- micro-frontends.org (yeni sekmede açılır) - Takımların bağımsız çalıştığı frontend geliştirme için uygulama pattern’leri ve entegrasyon stratejileri
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.
Bu serideki tüm yazılar
İlgili yazılar
Google'ın 2009'da çıkardığı Closure Compiler'ın modern web araçlarını nasıl şekillendirdiğini ve bugünkü build araçlarına kalıcı etkisini inceliyoruz.
javascript · build-tools · best-practices +6
Micro frontend kompozisyon stratejileri karşılaştırması: sunucu tarafı, build-zamanı, Module Federation ve iframe; ayrıca takasın karşılığını veren ekip ön koşulları.
react · tutorial · typescript
CDK TypeScript Lambda için 9 bundler ve 3 cdk synth runner'ının ölçümlü karşılaştırması; her katman için varsayılan ve onu seçtiren kural.
aws-cdk · lambda · typescript +3
Nub ve Vite+, 2026'nın oxc tabanlı iki Rust araç zinciri; rakip gibi görünüp öyle değiller. Hangi ikilinin hangi repoda yeri olduğuna dair net bir kural.
javascript · typescript · vite +3
Sunucu tarafı micro-frontend kompozisyonu, sayfa parçalarının sahipliğini bağımsız ekiplere verir. Zor kısım sahiplik sınırı ve sürümlenmiş tutarlılık sözleşmesidir.
build-tools · architecture · nextjs +2