← 목록으로 돌아가기

마이크로 프론트엔드(Micro-Frontends) 아키텍처 완벽 가이드: Module Federation부터 독립 배포 전략까지

Architecture

Micro-Frontends Architecture

마이크로 프론트엔드: 거대한 프론트엔드 모놀리스를 해체하는 기술

현대 웹 애플리케이션은 하나의 거대한 프론트엔드(Frontend Monolith)로 시작하여, 시간이 지남에 따라 수십만 줄의 코드와 수백 개의 컴포넌트를 품은 괴물로 성장하는 경우가 허다합니다. 이러한 모놀리식 프론트엔드는 초기에는 빠르게 개발할 수 있는 장점이 있지만, 규모가 커지면 빌드 시간의 폭증, 팀 간 코드 충돌, 그리고 기술 스택 고착화라는 치명적인 문제에 직면합니다.

백엔드에서는 이미 마이크로서비스 아키텍처(MSA)가 이러한 문제를 해결한 지 오래입니다. 하지만 프론트엔드는 여전히 하나의 거대한 SPA(Single Page Application)로 묶여 있는 경우가 많습니다. **마이크로 프론트엔드(Micro-Frontends)**는 바로 이 문제를 해결하기 위해 등장한 아키텍처 패턴입니다. 각 비즈니스 도메인별로 독립된 프론트엔드 애플리케이션을 만들고, 이를 하나의 통합된 사용자 경험으로 조합하는 것이 핵심입니다.

이 글에서는 마이크로 프론트엔드의 핵심 원리, 다양한 통합 방식(Module Federation, iframe, Web Components), 독립 배포 전략, 그리고 실무에서 마주하는 도전 과제와 해결책까지 6,000자 이상의 분량으로 심층적으로 다루겠습니다.

1. 마이크로 프론트엔드의 핵심 원칙

마이크로 프론트엔드 아키텍처는 다음 네 가지 핵심 원칙 위에 세워집니다.

1.1 기술 독립성 (Technology Agnostic)

각 마이크로 프론트엔드 팀은 자신만의 기술 스택을 자유롭게 선택할 수 있어야 합니다. A 팀은 React로, B 팀은 Vue로, C 팀은 Svelte로 개발하더라도 최종 사용자에게는 하나의 매끄러운 애플리케이션으로 보여야 합니다. 이는 기술 스택의 점진적 마이그레이션을 가능하게 합니다. Angular.js에서 React로의 전환을 한 번에 하는 대신, 새로운 기능부터 React로 만들고 기존 기능을 점진적으로 교체할 수 있습니다.

1.2 팀 코드 격리 (Isolated Team Code)

서로 다른 팀의 코드가 런타임에서 전역 변수를 공유하거나 상태를 직접 조작해서는 안 됩니다. 각 마이크로 프론트엔드는 자체적인 상태를 관리하고, 다른 마이크로 프론트엔드와는 명확하게 정의된 API나 이벤트 버스를 통해서만 소통해야 합니다.

1.3 독립 배포 (Independent Deployment)

각 마이크로 프론트엔드는 다른 마이크로 프론트엔드의 배포 상태에 영향을 받지 않고 독립적으로 빌드하고 배포할 수 있어야 합니다. 이것이야말로 마이크로 프론트엔드가 제공하는 가장 강력한 가치입니다. 결제 팀이 새로운 결제 수단을 추가할 때, 상품 팀이나 회원 팀의 코드를 건드릴 필요가 전혀 없습니다.

1.4 네이티브 브라우저 기능 우선 (Favor Native Browser Features)

프레임워크에 종속된 복잡한 통신 메커니즘 대신, 브라우저 네이티브 API인 Custom Events, History API, URL을 활용하여 마이크로 프론트엔드 간의 통신을 구현하는 것이 장기적으로 유지보수에 유리합니다.

2. 통합 방식의 심층 비교

마이크로 프론트엔드를 하나의 화면으로 조합하는 방식에는 여러 가지가 있으며, 각각의 트레이드오프를 정확히 이해해야 합니다.

2.1 빌드 타임 통합 (Build-Time Integration)

각 마이크로 프론트엔드를 npm 패키지로 배포하고, 컨테이너 앱에서 이를 의존성으로 설치하여 빌드하는 방식입니다. 장점은 구현이 단순하다는 것이지만, 단점은 어떤 마이크로 프론트엔드라도 변경되면 컨테이너 앱 전체를 다시 빌드하고 배포해야 한다는 것입니다. 이는 "독립 배포"라는 핵심 원칙을 위반하므로 대규모 프로젝트에는 권장되지 않습니다.

2.2 런타임 통합: Module Federation (Webpack 5+)

현재 가장 인기 있고 강력한 런타임 통합 방식입니다. Webpack 5에 내장된 Module Federation 플러그인은 여러 개의 독립적인 빌드가 하나의 애플리케이션을 형성하도록 해줍니다. 각 빌드는 컨테이너로 동작하면서 동시에 다른 컨테이너에서 모듈을 소비할 수 있습니다.

```javascript
// 리모트 앱 (결제 팀) webpack.config.js
new ModuleFederationPlugin({
name: 'payment',
filename: 'remoteEntry.js',
exposes: {
'./CheckoutForm': './src/components/CheckoutForm',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});

// 호스트 앱 (메인 셸) webpack.config.js
new ModuleFederationPlugin({
name: 'host',
remotes: {
payment: 'payment@https://payment.example.com/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
```

`shared` 옵션이 핵심입니다. React 같은 싱글턴 라이브러리가 중복 로딩되지 않도록 보장하면서, 각 마이크로 프론트엔드는 여전히 독립적으로 빌드됩니다.

2.3 런타임 통합: Web Components

Custom Elements를 사용하면 프레임워크에 구애받지 않고 UI 위젯을 배포할 수 있습니다. 각 마이크로 프론트엔드가 `<payment-checkout></payment-checkout>`처럼 커스텀 HTML 태그로 등록되면, 어떤 프레임워크에서든 이 태그를 사용하여 해당 기능을 삽입할 수 있습니다. Shadow DOM을 활용하면 스타일 격리까지 자연스럽게 달성됩니다.

2.4 서버 사이드 통합 (Server-Side Composition)

Nginx나 Edge 서버(Cloudflare Workers)에서 각 마이크로 프론트엔드의 HTML 조각을 조합하여 완성된 페이지를 내려보내는 방식입니다. 초기 로딩 성능(LCP)에 매우 유리하며, SSR(Server Side Rendering)이 필수인 SEO 민감한 페이지에 적합합니다.

3. 공유 상태와 팀 간 통신 전략

마이크로 프론트엔드 간에 데이터를 공유해야 하는 상황은 반드시 발생합니다. 예를 들어 사용자의 로그인 상태나 장바구니 아이템 수 같은 정보입니다.

3.1 이벤트 기반 통신 (Event Bus)

브라우저의 `CustomEvent` API를 사용하여 느슨하게 결합된 통신을 구현합니다. 각 마이크로 프론트엔드는 특정 이벤트를 발행(dispatch)하거나 구독(listen)할 수 있습니다. 이는 가장 단순하면서도 효과적인 방법입니다.

3.2 URL 기반 상태 공유

현재 필터 조건이나 페이지 상태를 URL 쿼리 파라미터에 인코딩하는 것은 가장 선언적이고 표준적인 상태 공유 방법입니다. 모든 마이크로 프론트엔드가 URL을 읽어 자신의 상태를 동기화할 수 있습니다.

3.3 공유 상태 저장소

로그인 토큰 같은 전역 상태는 `localStorage` 또는 쿠키에 저장하고, 각 마이크로 프론트엔드가 이를 읽어 사용합니다. 단, 직접적인 전역 상태 라이브러리(Redux 등)의 공유는 절대 피해야 합니다. 이는 결합도를 높여 독립 배포를 불가능하게 만듭니다.

4. 스타일 격리와 디자인 시스템 통합

여러 팀이 만든 UI가 하나의 페이지에 공존할 때 CSS 충돌은 피할 수 없는 문제입니다. 이를 해결하는 전략은 다음과 같습니다.

  • CSS Modules / CSS-in-JS: 컴포넌트 수준에서 스코프를 가진 스타일을 생성합니다.
  • Shadow DOM: Web Components를 사용할 경우, Shadow DOM이 완벽한 스타일 격리를 제공합니다.
  • 공유 디자인 시스템: npm 패키지로 배포되는 공통 디자인 토큰(색상, 간격, 타이포그래피)과 기본 컴포넌트(Button, Input)를 통해 시각적 일관성을 유지합니다. 각 팀은 이 디자인 시스템을 의존성으로 사용하되, UI 로직은 독립적으로 개발합니다.

5. 독립 배포 파이프라인 구축

마이크로 프론트엔드의 꽃은 독립 배포입니다. 각 팀은 자체 CI/CD 파이프라인을 보유하며, 변경 사항을 메인 애플리케이션 전체를 재배포하지 않고도 즉시 프로덕션에 반영할 수 있어야 합니다.

  • CDN 기반 배포: 각 마이크로 프론트엔드의 빌드 결과물(remoteEntry.js)를 S3/CloudFront 같은 CDN에 배포합니다.
  • 버전 관리: 파일명에 해시를 포함시키거나(content hashing), 별도의 manifest 파일로 최신 버전을 관리합니다.
  • 롤백 전략: 문제가 발생하면 이전 버전의 remoteEntry.js를 가리키도록 빠르게 전환할 수 있어야 합니다.

6. 실무에서의 도전 과제

6.1 성능 오버헤드

여러 프레임워크 런타임이 동시에 로딩되면 번들 크기가 커질 수 있습니다. Module Federation의 shared 옵션으로 공통 라이브러리를 싱글턴으로 관리하고, 각 마이크로 프론트엔드를 Lazy Loading하여 초기 로딩에 필요한 코드만 내려보내야 합니다.

6.2 테스팅 전략

각 마이크로 프론트엔드는 독립적으로 단위/통합 테스트를 수행하고, 전체 통합은 E2E 테스트(Playwright, Cypress)로 검증합니다. 컨트랙트 테스트(Contract Testing)를 도입하면 마이크로 프론트엔드 간의 인터페이스 변경을 사전에 감지할 수 있습니다.

6.3 일관된 사용자 경험

서로 다른 팀이 만든 UI가 이질적으로 느껴지지 않도록, 공유 디자인 시스템 라이브러리와 디자인 리뷰 프로세스를 반드시 갖추어야 합니다.

7. 실무 도입 사례와 성능 모니터링

7.1 점진적 마이그레이션 전략

기존 모놀리식 프론트엔드를 한꺼번에 마이크로 프론트엔드로 전환하는 것은 극도로 위험합니다. 가장 효과적인 접근법은 **스트랭글러 패턴(Strangler Fig Pattern)**입니다. 새로운 기능부터 마이크로 프론트엔드로 개발하고, 기존 레거시를 점진적으로 교체해 나가는 것입니다. 예를 들어, 기존 모놀리스의 결제 페이지를 Module Federation 기반의 독립 마이크로 프론트엔드로 먼저 분리하고, 성공적으로 운영되면 다음으로 상품 목록 페이지를 분리하는 식입니다. 이 과정에서 기존 라우터와 새로운 마이크로 프론트엔드 라우터가 공존하는 하이브리드 기간이 발생하는데, 이를 관리하기 위한 라우팅 레이어 설계가 매우 중요합니다.

7.2 성능 모니터링과 오류 추적

여러 팀이 독립적으로 배포하는 환경에서는 특정 마이크로 프론트엔드의 성능 저하나 오류가 전체 사용자 경험에 영향을 줄 수 있습니다. 따라서 각 마이크로 프론트엔드별로 독립적인 성능 메트릭(LCP, CLS, INP)을 수집하고, 오류 발생 시 어떤 마이크로 프론트엔드에서 문제가 발생했는지 즉시 식별할 수 있는 분산 추적(Distributed Tracing) 시스템을 갖추어야 합니다. Sentry, Datadog 같은 모니터링 도구에 마이크로 프론트엔드 식별 태그를 부착하면, 장애의 근본 원인(Root Cause)을 빠르게 파악하고 해당 팀에 즉시 알림을 보낼 수 있습니다.

7.3 버전 호환성과 API 계약

마이크로 프론트엔드 간의 통신 인터페이스가 변경되면 호환성 문제가 발생할 수 있습니다. 이를 방지하기 위해 Pact 같은 컨슈머 주도 계약 테스트(Consumer-Driven Contract Testing) 도구를 CI 파이프라인에 통합하는 것이 모범 사례입니다.

8. 결론: 조직 구조의 프론트엔드적 반영

콘웨이의 법칙(Conway's Law)은 "소프트웨어 구조는 그것을 만드는 조직의 커뮤니케이션 구조를 반영한다"고 말합니다. 마이크로 프론트엔드는 이 법칙을 역으로 활용합니다. 독립적인 비즈니스 도메인 팀 구조에 맞춰 프론트엔드 아키텍처를 설계함으로써, 각 팀의 자율성과 배포 속도를 극대화하는 것입니다.

물론 모든 프로젝트에 마이크로 프론트엔드가 필요한 것은 아닙니다. 소규모 팀이나 단일 제품에는 오버 엔지니어링이 될 수 있습니다. 하지만 3개 이상의 독립적인 팀이 하나의 프론트엔드 제품을 공동으로 개발하고 있고, 배포 병목이 비즈니스 속도를 저해하고 있다면, 마이크로 프론트엔드는 그 고통을 해소하는 가장 구조적이고 확장 가능한 해답이 될 것입니다.