Matheus Breguêz (matbrgz)
Micro-frontends: Quando e Por Que Adotar em Projetos Escaláveis
Desenvolvimento

Micro-frontends: Quando e Por Que Adotar em Projetos Escaláveis

Índice

Micro-frontends: Quando e Por Que Adotar em Projetos Escaláveis

Em 2025, com aplicações web cada vez mais complexas, a arquitetura de micro-frontends tornou-se uma solução madura para escalar tanto o desenvolvimento quanto a manutenção de grandes aplicações. Vamos explorar quando e como implementar essa arquitetura de forma eficiente.

Fundamentos de Micro-frontends

1. Conceitos Básicos

// Exemplo de configuração de Module Federation
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        marketing: 'marketing@http://localhost:8081/remoteEntry.js',
        dashboard: 'dashboard@http://localhost:8082/remoteEntry.js',
        auth: 'auth@http://localhost:8083/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
};

2. Arquitetura Típica

graph TD
    A[Container Application] -->|Load| B[Marketing MFE]
    A -->|Load| C[Dashboard MFE]
    A -->|Load| D[Auth MFE]
    B -->|Use| E[Shared Components]
    C -->|Use| E
    D -->|Use| E

Quando Adotar Micro-frontends

1. Indicadores de Necessidade

interface ProjectAssessment {
  teamSize: number;
  numberOfFeatures: number;
  deploymentFrequency: number;
  technicalDebt: {
    monolithComplexity: number;
    buildTime: number;
    testCoverage: number;
  };
  teamAutonomy: {
    independent: boolean;
    technicalDecisions: boolean;
    deploymentControl: boolean;
  };
}

function shouldAdoptMicroFrontends(
  assessment: ProjectAssessment
): RecommendationReport {
  const score = calculateAdoptionScore(assessment);
  
  return {
    recommendation: score > 0.7 ? 'Adopt' : 'Evaluate Further',
    reasons: analyzeFactors(assessment),
    risks: identifyRisks(assessment),
    nextSteps: generateRoadmap(assessment),
  };
}

2. Análise de Trade-offs

Aspecto Benefícios Desafios
Escalabilidade Times independentes Complexidade inicial
Performance Carregamento sob demanda Overhead de runtime
Manutenção Código isolado Consistência entre apps
Deploy Independente Coordenação necessária
Desenvolvimento Autonomia Curva de aprendizado

Implementação com Module Federation

1. Container Application

// src/App.tsx
import { lazy, Suspense } from 'react';
import { Router, Route, Switch } from 'react-router-dom';

const MarketingApp = lazy(() => import('./remotes/MarketingApp'));
const DashboardApp = lazy(() => import('./remotes/DashboardApp'));
const AuthApp = lazy(() => import('./remotes/AuthApp'));

export default function App() {
  return (
    <Router>
      <Suspense fallback={<LoadingSpinner />}>
        <Switch>
          <Route path="/auth" component={AuthApp} />
          <Route path="/dashboard" component={DashboardApp} />
          <Route path="/" component={MarketingApp} />
        </Switch>
      </Suspense>
    </Router>
  );
}

// src/bootstrap.tsx
import { createRoot } from 'react-dom/client';
import App from './App';

const mount = (el: HTMLElement) => {
  const root = createRoot(el);
  root.render(<App />);
};

const devRoot = document.querySelector('#root');
if (devRoot) {
  mount(devRoot);
}

export { mount };

2. Remote Applications

// marketing/src/bootstrap.tsx
import { createRoot } from 'react-dom/client';
import { createMemoryHistory, createBrowserHistory } from 'history';
import App from './App';

interface MountOptions {
  defaultHistory?: typeof createMemoryHistory;
  initialPath?: string;
  onNavigate?: (location: any) => void;
}

const mount = (
  el: HTMLElement,
  {
    defaultHistory,
    initialPath,
    onNavigate,
  }: MountOptions = {}
) => {
  const history = defaultHistory?.({
    initialEntries: [initialPath || '/'],
  }) || createBrowserHistory();
  
  if (onNavigate) {
    history.listen(onNavigate);
  }
  
  const root = createRoot(el);
  root.render(<App history={history} />);
  
  return {
    onParentNavigate({ pathname: nextPathname }) {
      const { pathname } = history.location;
      if (pathname !== nextPathname) {
        history.push(nextPathname);
      }
    },
  };
};

if (process.env.NODE_ENV === 'development') {
  const devRoot = document.querySelector('#_marketing-dev-root');
  if (devRoot) {
    mount(devRoot, { defaultHistory: createBrowserHistory });
  }
}

export { mount };

3. Compartilhamento de Estado

// shared/store/index.ts
import create from 'zustand';

interface SharedState {
  user: User | null;
  theme: 'light' | 'dark';
  setUser: (user: User | null) => void;
  setTheme: (theme: 'light' | 'dark') => void;
}

const useSharedStore = create<SharedState>((set) => ({
  user: null,
  theme: 'light',
  setUser: (user) => set({ user }),
  setTheme: (theme) => set({ theme }),
}));

export { useSharedStore };

// shared/events/index.ts
type EventCallback = (data: any) => void;

class EventBus {
  private events: Map<string, Set<EventCallback>>;
  
  constructor() {
    this.events = new Map();
  }
  
  subscribe(event: string, callback: EventCallback) {
    if (!this.events.has(event)) {
      this.events.set(event, new Set());
    }
    this.events.get(event)!.add(callback);
    
    return () => {
      this.events.get(event)?.delete(callback);
    };
  }
  
  publish(event: string, data: any) {
    this.events.get(event)?.forEach((callback) => {
      callback(data);
    });
  }
}

export const eventBus = new EventBus();

Padrões de Design

1. Composição de Aplicações

// container/src/components/MicroFrontend.tsx
interface MicroFrontendProps {
  name: string;
  host: string;
  history?: History;
}

function MicroFrontend({ name, host, history }: MicroFrontendProps) {
  useEffect(() => {
    const scriptId = `micro-frontend-script-${name}`;
    
    const renderMicroFrontend = () => {
      // @ts-ignore
      window[`render${name}`](
        `${name}-container`,
        history
      );
    };
    
    if (document.getElementById(scriptId)) {
      renderMicroFrontend();
      return;
    }
    
    fetch(`${host}/asset-manifest.json`)
      .then((res) => res.json())
      .then((manifest) => {
        const script = document.createElement('script');
        script.id = scriptId;
        script.crossOrigin = '';
        script.src = `${host}${manifest.files['main.js']}`;
        script.onload = () => {
          renderMicroFrontend();
        };
        document.head.appendChild(script);
      });
    
    return () => {
      // @ts-ignore
      window[`unmount${name}`]?.(`${name}-container`);
    };
  }, []);
  
  return <div id={`${name}-container`} />;
}

2. Comunicação Entre Apps

// shared/communication/index.ts
type MessageHandler = (data: any) => void;

class MessageBus {
  private handlers: Map<string, Set<MessageHandler>>;
  
  constructor() {
    this.handlers = new Map();
    
    window.addEventListener('message', (event) => {
      const { type, data } = event.data;
      this.handlers.get(type)?.forEach((handler) => {
        handler(data);
      });
    });
  }
  
  subscribe(type: string, handler: MessageHandler) {
    if (!this.handlers.has(type)) {
      this.handlers.set(type, new Set());
    }
    this.handlers.get(type)!.add(handler);
    
    return () => {
      this.handlers.get(type)?.delete(handler);
    };
  }
  
  publish(type: string, data: any) {
    window.postMessage({ type, data }, '*');
  }
}

export const messageBus = new MessageBus();

Melhores Práticas

1. Performance

// shared/performance/index.ts
interface PerformanceMetrics {
  ttfb: number;
  fcp: number;
  lcp: number;
  fid: number;
  cls: number;
}

class PerformanceMonitor {
  private metrics: Map<string, PerformanceMetrics>;
  
  constructor() {
    this.metrics = new Map();
    this.setupObservers();
  }
  
  private setupObservers() {
    new PerformanceObserver((entryList) => {
      for (const entry of entryList.getEntries()) {
        this.recordMetric(entry);
      }
    }).observe({ entryTypes: ['web-vital'] });
  }
  
  private recordMetric(entry: PerformanceEntry) {
    const appName = this.getAppName();
    if (!this.metrics.has(appName)) {
      this.metrics.set(appName, {
        ttfb: 0,
        fcp: 0,
        lcp: 0,
        fid: 0,
        cls: 0,
      });
    }
    
    const metrics = this.metrics.get(appName)!;
    metrics[entry.name as keyof PerformanceMetrics] = entry.value;
  }
}

2. Testes

// shared/testing/index.ts
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';

interface TestConfig {
  route?: string;
  initialState?: any;
  mocks?: any[];
}

function renderWithRouter(
  ui: React.ReactElement,
  {
    route = '/',
    initialState = {},
    mocks = [],
  }: TestConfig = {}
) {
  const history = createMemoryHistory({
    initialEntries: [route],
  });
  
  return {
    ...render(ui),
    history,
  };
}

export { renderWithRouter };

Desafios e Soluções

1. Consistência Visual

// shared/design-system/index.ts
import { createTheme, ThemeProvider } from '@mui/material/styles';

const baseTheme = createTheme({
  palette: {
    primary: {
      main: '#1976d2',
    },
    secondary: {
      main: '#dc004e',
    },
  },
  typography: {
    fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
  },
  components: {
    MuiButton: {
      styleOverrides: {
        root: {
          textTransform: 'none',
        },
      },
    },
  },
});

export { baseTheme, ThemeProvider };

2. Monitoramento

// shared/monitoring/index.ts
interface MonitoringConfig {
  appName: string;
  version: string;
  environment: string;
}

class ApplicationMonitor {
  constructor(private config: MonitoringConfig) {
    this.setupErrorBoundary();
    this.setupPerformanceMonitoring();
    this.setupUserMonitoring();
  }
  
  private setupErrorBoundary() {
    window.addEventListener('error', (event) => {
      this.logError({
        type: 'uncaught',
        error: event.error,
        location: event.filename,
        line: event.lineno,
        column: event.colno,
      });
    });
  }
  
  private logError(error: any) {
    console.error('[MFE Error]', {
      ...error,
      app: this.config.appName,
      version: this.config.version,
      environment: this.config.environment,
      timestamp: new Date().toISOString(),
    });
  }
}

Conclusão

A adoção de micro-frontends oferece benefícios significativos para projetos escaláveis:

  1. Escalabilidade Organizacional: Times autônomos e independentes
  2. Flexibilidade Técnica: Liberdade de escolha de tecnologias
  3. Deploy Independente: Redução de riscos e maior velocidade
  4. Manutenibilidade: Código mais organizado e isolado
  5. Evolução Gradual: Possibilidade de migração incremental

Próximos Passos

  1. Avalie a necessidade real do seu projeto
  2. Comece com um piloto em área não crítica
  3. Estabeleça padrões e governança
  4. Invista em automação e ferramentas
  5. Monitore e ajuste continuamente

Está considerando adotar micro-frontends em seu projeto? Compartilhe suas dúvidas e experiências nos comentários abaixo!

Micro-frontends Arquitetura Frontend Escalabilidade React Module Federation

Compartilhe este artigo

Transforme suas ideias em realidade

Vamos trabalhar juntos para criar soluções inovadoras que impulsionem seu negócio.