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

AspectoBenefíciosDesafios
EscalabilidadeTimes independentesComplexidade inicial
PerformanceCarregamento sob demandaOverhead de runtime
ManutençãoCódigo isoladoConsistência entre apps
DeployIndependenteCoordenação necessária
DesenvolvimentoAutonomiaCurva 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.